刚下了处cp APP里面的游戏处cp什么意思为什么都玩不起来 都说我服务器太慢 无论什么时候 求解决方案

资料图鉴:教程速查:
当前位置:
我的世界五橙CP玩巨人 服务器有毒啊
小编:3F时间: 14:21
  我的世界五橙CP玩巨人 服务器有毒啊。那下面则是五歌和橙子组CP玩的巨人视频哦~那有感兴趣的玩家不妨进来看看下面的这个视频吧!总结下来就是一句话:服务器有毒啊!
  游戏园我的世界官方群: 或 &欢迎各路喜爱我的世界的小伙伴们加入讨论!
  玩服务器的小伙伴们可以加入:&群一起联机玩游戏哦!
  如果你是腐竹的话可以给我们投稿你的服务器哦~投稿地址:
  如果你有心仪的作品或者心得分享的话,欢迎来游戏园投稿,大家可以点击&&&&&&进行投稿哦~&有奖品哦~
  PS:在此非常感谢我的世界玩家五之歌的分享。
  以上就是我的世界五橙CP玩巨人 服务器有毒啊。更多精彩尽在游戏园我的世界专区。
  相关攻略推荐:
分享到:更多
类型:休闲娱乐平台:PC,iOS,安卓
游戏大礼包手游开测表
挂机吧主公五四土豪青年礼包九阴真经3D五四大好青年礼包《皇图》五四青年节礼包HIT:我守护的一切五一限量金币礼包《口袋妖怪复刻》五一节礼包笑傲天龙五一飞速升级礼包
05-17封测05-17封测05-16公测05-04内测04-29内测04-28公测04-23公测04-22内测04-22封测04-14公测
攻略推荐本月最新
手游排行网游单机RTS游戏有很多,可能大家比较熟悉的有Warcraft III (dota)和 StarCraft,&br&早期西木的沙丘,红色警戒更是rts游戏的鼻祖,带给我们无限的欢乐和回忆。&br&还有当下比较流行lol与dota2,实际上都是孙子辈的游戏了。&br&&br&那么他们到底是怎么做到高频操作又同步的呢?&br&&br&&b&同步机制&/b&&br&&br&假设游戏中A,B两个玩家移动,并同时向对方发出射击指令&br&如果没有合适的同步机制&br&那么可能出现的情况有&br&1 A屏幕显示B已经被杀死,B屏幕显示A已经被杀死&br&2 或者在瞄准后确打不到对方&br&图中玩家Plyaer1,Plyaer2在两个不同的客户端,表现出不同效果&br&&img src=&/6acdc512a5f34613fcd8_b.png& data-rawwidth=&554& data-rawheight=&433& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&/6acdc512a5f34613fcd8_r.png&&&br&&br&因为网络是有延时的,而每个玩家的网络情况都不尽相同。&br&还有每帧渲染的延迟(早期的计算机性能不够好的时候会出现这个问题)&br&同步机制最重要的作用就是解决延迟等可能发生不一致的情况。&br&&br&&b&同步机制的分类&/b&&br&&b&Peer-to-peer模式&/b&: 没有服务器,每个玩家互相连接,各自模拟整个流程.典型的lockstep模式&br&优点:减少主机带来的延时&br&缺点:容易作弊&br&&img src=&/77bb6f1cb5bea2cbaecd45_b.png& data-rawwidth=&495& data-rawheight=&286& class=&origin_image zh-lightbox-thumb& width=&495& data-original=&/77bb6f1cb5bea2cbaecd45_r.png&&&br&&b&Client-Server模式&/b&&br&所有的操作需经过服务器确认后才能进行客户端模拟,如arpg传奇类都是此架构,如果延时高就会有明显的卡顿。&br&优点:服务器是绝对的权威,可以防止作弊,可以做更多的管理与限制&br&缺点:服务器变的更复杂,服务器断线,所有玩家断线,属于服务器依赖型。&br&&br&&img src=&/a6f6fdbcac_b.png& data-rawwidth=&448& data-rawheight=&286& class=&origin_image zh-lightbox-thumb& width=&448& data-original=&/a6f6fdbcac_r.png&&&br&&br&早期的RTS游戏大多采用Lockstep方案来设计,像罗马帝国,沙丘之类。&br&Lockstep最早用于军队中&br&&img src=&/171d4c617b5a16f69928b76ace204374_b.png& data-rawwidth=&494& data-rawheight=&341& class=&origin_image zh-lightbox-thumb& width=&494& data-original=&/171d4c617b5a16f69928b76ace204374_r.png&&&br&就是说玩家的数据每个时间段同步一次,同步的走。&br&&br&&br&标准的lockstep模式&br&1 每个玩家互相连接,整个游戏过程划分成一组turn指令帧,由玩家自我模拟&br&2 游戏速度取决于网络最慢的那个玩家&br&3 一个玩家掉线不会影响到其他玩家&br&&br&&b&什么是Turn?&/b&&br&一个turn可以理解成1个回合,相信大家都玩过回合制游戏吧&br&只是这个turn非常短,大概100MS-200MS&br&玩家相互之间发送的指令在每个turn间隔发出&br&&br&&img src=&/ce391a087c4_b.png& data-rawwidth=&328& data-rawheight=&173& class=&content_image& width=&328&&每个玩家只需要接收指令,并在本地播放指令就可以啦&br&&br&War3如何运算伤害?&br&玩家到底是发送什么指令到主机,主机到底参与了什么计算呢?&br&实际上玩家都只需要发送基本的指令如选择单位,移动单位,使用技能1234,点击物品栏1-6,可以通过APM查看软件看到一些基本操作事件&br&&img src=&/2dbc77a4df380ebf6329b1_b.png& data-rawwidth=&540& data-rawheight=&566& class=&origin_image zh-lightbox-thumb& width=&540& data-original=&/2dbc77a4df380ebf6329b1_r.png&&&br&也就是说所有的一切伤害计算都是在本地计算完成的&br&包括伤害,暴击,命中,刷怪等,只要初始化好随机数种子就可以啦&br&玩家只是发送操作指令,如点击坐标(0,1, 0),左键框选(100,100,50,50)等&br&每个玩家都在模拟全部的流程&br&&br&那么War3到底算不算使用lockstep模式,或者是特殊的client-server?&br&其实可以通过几个问题判断出&br&1 非主机玩家卡是否可以影响到其他玩家,如果不会,那么更可能是client-server模式&br&2 可以通过抓包工具拦截网络数据包的流向,来判断是否是peer to peer的连接方式还是只连接到主机(或通过主机强制掉线方式判断)。&br&一个外国朋友的回答&br&&br&&img src=&/f2e3cd93ccbd85cabb8b_b.png& data-rawwidth=&728& data-rawheight=&221& class=&origin_image zh-lightbox-thumb& width=&728& data-original=&/f2e3cd93ccbd85cabb8b_r.png&&&br&个人也认为War3是基于Client-Server的一种的特殊模式,主机肯定需要验证一些逻辑。&br&主机负责广播每个client的指令&br&这存在两个问题&br&&ol&&li&本机(非主机)发出的指令,如果超时或者丢包,是否直接丢弃?&br&&/li&&li&其他玩家的指令,主机转发未成功确认,如何处理?&br&&/li&&/ol&&br&第一个问题&br&&ol&&li&如果是本机(非主机)发出的指令超时,可以直接丢弃.(如果不丢弃,其他玩家就必须等待结果,这样会导致挂起,而且会非常频繁,这里还有udp协议容易丢包的原因,但是war3好像并没有经常性的挂起)&/li&&/ol&&br& 还有一种可能,客户端得知之前的turn没有发送成功,把当前这轮的指令和上一轮的指令进行合并,然后一起发出,这样本地客户端就不会有任何的异样了。&br&&br&例如玩家移动到A后再移动到B&br&上个turn的指令是移动到A点,但是没有发成功,下个turn的指令先移动到A,再移动到B,这样在客户端就不会有丢失的感觉啦,还是可以正常的模拟而不会影响到其他玩家。&br&&br&2. 收其他玩家的指令超时,那么属于我们自身网络的问题,如果丢弃必将导致游戏进程不同步,所以服务器必须将他们的turn指令都缓存起来,&br&或者缓存一部分turn指令集,在我网络稳定的时候,把丢失的那一部分turn指令集发给我,而我只需要下载那个list加快gameupdate就好啦。&br&&br&有些朋友问到外挂的问题&br&相信玩过魔兽的人基本都用过,实际上像战争迷雾,显示单位等只会保存一个状态值在内存中,只要定位到内存地址,改一下变量值就好了,一般是服务器是不会检测这个的。&br&而攻击力,道具数量等,由于大家都需要模拟,你本地修改了,会影响到其他人,程序就会发生蝴蝶效应。&br&开图挂应该是这类游戏最常见的了。&br&&img src=&/a429d14114baeab904248_b.png& data-rawwidth=&794& data-rawheight=&385& class=&origin_image zh-lightbox-thumb& width=&794& data-original=&/a429d14114baeab904248_r.png&&&img src=&/056f8f323c8f_b.png& data-rawwidth=&823& data-rawheight=&353& class=&origin_image zh-lightbox-thumb& width=&823& data-original=&/056f8f323c8f_r.png&&&br&&br&至于现在非常流行的 Dota2 和 英雄联盟,会额外的加入更多服务器来验证和计算一些外部数据,&br&但内部原理是一致的,早期的游戏与现在的网游不可同日而语。&br&欢迎各游戏圈朋友加-Q群
RTS游戏有很多,可能大家比较熟悉的有Warcraft III (dota)和 StarCraft, 早期西木的沙丘,红色警戒更是rts游戏的鼻祖,带给我们无限的欢乐和回忆。 还有当下比较流行lol与dota2,实际上都是孙子辈的游戏了。 那么他们到底是怎么做到高频操作又同步的呢? …
&p&游戏服务器常常有心跳包的设计。&/p&&br&&p&我们的心跳包就是为了防止Socket断开连接,或是TCP的连接断开吗?&/p&&br&&p&答案是否定的,TCP连接的通道是个虚拟的,连接的维持靠的是两端TCP软件对连接状态的维护。&/p&&p&TCP 连接自身有维护连接的机制,说白了就是自身有长时间没有数据包情况下的判断连接是否还存在的检测,清除死连接,即使在没有数据来往的时候,TCP也就可以(在启动TCP这个功能的前提下)自动发包检测是否连接正常,这个不需要我们处理。&/p&&br&&b&服务端设计心跳包的目的:&/b&&br&探知对端应用是否存活,服务端客户端都可以发心跳包,一般都是客户端发送心跳包,服务端用于判断客户端是否在线,从而对服务端内存缓存数据进行清理(玩家下线等);问题在于,通过TCP四次握手断开的设定,我们也是可以通过Socket的read方法来判断TCP连接是否断开,从而做出相应的清理内存动作,那么为什么我们还需要使用客户端发送心跳包来判断呢?&br&&br&&b&第一种判断客户端是否在线策略:&/b&&br&&b&直接监控TCP传输协议的返回值,通过返回值处理应用层的存活判断&/b&&br&&b&比如在C++当中&/b&&br&&b&使用poll的IO复用方法时:&/b&&br&&b&if(fds[i].revents & POLLERR) &/b&&br&&b&if(fds[i].events & POLLDHUP)&/b&&br&&b&通过上述判断可以探知TCP连接的正确性从而在服务器也关闭对应的连接&/b&&br&close() on the file descriptor will release resources that are still being reserved on behalf of the socket. 此时调用close()函数才会释放相关的资源。&br&&br&比如说进行如下处理:&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&cm&&/*如果客户端关闭连接,则服务器也关闭对应的连接,并将用户总数减1*/&/span&
&span class=&n&&users&/span&&span class=&p&&[&/span&&span class=&n&&fds&/span&&span class=&p&&[&/span&&span class=&n&&i&/span&&span class=&p&&].&/span&&span class=&n&&fd&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&users&/span&&span class=&p&&[&/span&&span class=&n&&fds&/span&&span class=&p&&[&/span&&span class=&n&&user_counter&/span&&span class=&p&&].&/span&&span class=&n&&fd&/span&&span class=&p&&];&/span&
&span class=&n&&close&/span&&span class=&p&&(&/span&&span class=&n&&fds&/span&&span class=&p&&[&/span&&span class=&n&&i&/span&&span class=&p&&].&/span&&span class=&n&&fd&/span&&span class=&p&&);&/span&
&span class=&n&&fds&/span&&span class=&p&&[&/span&&span class=&n&&i&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&fds&/span&&span class=&p&&[&/span&&span class=&n&&user_counter&/span&&span class=&p&&];&/span&
&span class=&n&&i&/span&&span class=&o&&--&/span&&span class=&p&&;&/span&
&span class=&n&&user_counter&/span&&span class=&o&&--&/span&&span class=&p&&;&/span&
&span class=&n&&printf&/span&&span class=&p&&(&/span&&span class=&s&&&a client left&/span&&span class=&se&&\n&/span&&span class=&s&&&&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&br&&b&又比如在Java中:&/b&&br&&b&在Java的阻塞编程中:通过&/b&&br&&b&&div class=&highlight&&&pre&&code class=&language-java&& &span class=&n&&ServerSocket&/span& &span class=&n&&ss&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&ServerSocket&/span&&span class=&o&&(&/span&&span class=&mi&&10021&/span&&span class=&o&&);&/span&
&span class=&n&&Socket&/span& &span class=&n&&so&/span& &span class=&o&&=&/span& &span class=&n&&ss&/span&&span class=&o&&.&/span&&span class=&na&&accept&/span&&span class=&o&&();&/span&
&span class=&c1&&// 获取相关流对象&/span&
&span class=&n&&InputStream&/span& &span class=&n&&in&/span& &span class=&o&&=&/span& &span class=&n&&so&/span&&span class=&o&&.&/span&&span class=&na&&getInputStream&/span&&span class=&o&&();&/span&
&span class=&kt&&byte&/span&&span class=&o&&[]&/span& &span class=&n&&bytes&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&kt&&byte&/span&&span class=&o&&[&/span&&span class=&mi&&1024&/span&&span class=&o&&];&/span&
&span class=&kt&&int&/span& &span class=&n&&num&/span& &span class=&o&&=&/span& &span class=&n&&in&/span&&span class=&o&&.&/span&&span class=&na&&read&/span&&span class=&o&&(&/span&&span class=&n&&bytes&/span&&span class=&o&&);&/span&
&span class=&k&&if&/span&&span class=&err&&(&/span&&span class=&n&&num&/span& &span class=&o&&==&/span& &span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&err&&)&/span&&span class=&c1&&//表明读到了流的末尾,事实也就是client端断开了连接,比如调用close()&/span&
&span class=&o&&{&/span&
&span class=&n&&so&/span&&span class=&o&&.&/span&&span class=&na&&close&/span&&span class=&o&&();&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&在Java的非阻塞编程当中:通过&/b&&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&n&&SelectionKey&/span& &span class=&n&&key&/span& &span class=&o&&=&/span& &span class=&n&&selector&/span&&span class=&o&&.&/span&&span class=&na&&register&/span&&span class=&err&&(&/span&&span class=&n&&socketChannel&/span&&span class=&o&&,&/span&&span class=&n&&ops&/span&&span class=&o&&,&/span&&span class=&n&&handle&/span&&span class=&err&&)&/span&&span class=&o&&;&/span&
&span class=&n&&SocketChannel&/span& &span class=&n&&socketChanel&/span& &span class=&o&&=&/span& &span class=&err&&(&/span&&span class=&n&&SocketChannel&/span&&span class=&err&&)&/span&&span class=&n&&key&/span&&span class=&o&&.&/span&&span class=&na&&channel&/span&&span class=&o&&();&/span&
&span class=&n&&ByteBuffer&/span& &span class=&n&&buffer&/span& &span class=&o&&=&/span& &span class=&n&&ByteBuffer&/span&&span class=&o&&.&/span&&span class=&na&&allocate&/span&&span class=&err&&(&/span&&span class=&mi&&1024&/span&&span class=&err&&);&/span&
&span class=&kt&&int&/span& &span class=&n&&num&/span& &span class=&o&&=&/span& &span class=&n&&socketChannel&/span&&span class=&o&&.&/span&&span class=&na&&read&/span&&span class=&o&&(&/span&&span class=&n&&buffer&/span&&span class=&o&&);&/span&
&span class=&k&&if&/span&&span class=&o&&(&/span&&span class=&n&&num&/span& &span class=&o&&==&/span& &span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&o&&)&/span&
&span class=&o&&{&/span&
&span class=&n&&key&/span&&span class=&o&&.&/span&&span class=&na&&channel&/span&&span class=&o&&().&/span&&span class=&na&&close&/span&&span class=&o&&();&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&上述连接处理方式,返回-1也好,收到POLLERR POLLDHUP也好,都是收到了客户端的fin或者rst之后的反应,所以根据四次分手原则,我们调用close方法,发送fin给客户端。&br&&br&&br&上面这种策略通过TCP协议的返回值来得知客户端TCP断开,从而得知客户端掉线。&br&当前提是&b&如果提前根据ip或者mac做了记录&/b&,所以可以&b&在服务器端收到TCP连接中断的消息后,调用close,并且通过socket得到玩家socket数据(具体如IP地址),从而获得user信息从而清除数据。&/b&&br&&br&&br&&b&那么这种方式有什么不完美呢?或者说有什么缺陷呢?&/b&&br&&br&&br&&b&主要原因就是TCP的断开可能有时候是不能瞬时探知的,甚至是不能探知的,也可能有很长时间的延迟,如果前端没有正常的断开TCP连接,四次握手没有发起,服务端无从得知客户端的掉线,那么就要依靠开启TCP的keep alive机制,but TCP协议的keep alive机制是不建议开启的,即使开启了默认的时间间隔是2h,泪奔!如果要服务端维持一个2h的死链接,那是可怕的,如果我们调整了时间间隔,也是有problem的,因为TCP本身就不建议TCP层的心跳检测,因为这可能导致一个完好的TCP连接在中间网络中断的情况下重启⊙▂⊙。&/b&&br&&br&&br&&b&下面有必要先介绍下TCP的keep-alive机制。&/b&&br&关于TCP自己的keep-alive机制,参看TCP/IP详解当中有如下介绍:&br&使用keeplive机制,可以检测到网络异常。&br&&b&一、什么是keepalive定时器?&br&&/b&&br&在一个空闲的(idle)TCP连接上,没有任何的数据流,许多TCP/IP的初学者都对此感到惊奇。也就是说,如果TCP连接两端没有任何一个进程在向对方发送数据,那么在这两个TCP模块之间没有任何的数据交换。你可能在其它的网络协议中发现有轮询(polling),但在TCP中它不存在。言外之意就是我们只要启动一个客户端进程,同服务器建立了TCP连接,不管你离开几小时,几天,几星期或是几个月,连接依旧存在。中间的路由器可能崩溃或者重启,电话线可能go down或者back up,只要连接两端的主机没有重启,连接依旧保持建立。&br&&br&这就可以认为不管是客户端的还是服务器端的应用程序都没有应用程序级(application-level)的定时器来探测连接的不活动状态(inactivity),从而引起任何一个应用程序的终止。然而有的时候,服务器需要知道客户端主机是否已崩溃并且关闭,或者崩溃但重启。许多实现提供了存活定时器来完成这个任务。&br&&br&存活定时器是一个包含争议的特征。许多人认为,即使需要这个特征,这种对对方的轮询也应该由应用程序来完成,而不是由TCP中实现。此外,如果两个终端系统之间的某个中间网络上有连接的暂时中断,那么存活选项(option)就能够引起两个进程间一个良好连接的终止。例如,如果正好在某个中间路由器崩溃、重启的时候发送存活探测,TCP就将会认为客户端主机已经崩溃,但事实并非如此。&br&&br&存活(keepalive)并不是TCP规范的一部分。在Host Requirements RFC罗列有不使用它的三个理由:(1)在短暂的故障期间,它们可能引起一个良好连接(good connection)被释放(dropped),(2)它们消费了不必要的宽带,(3)在以数据包计费的互联网上它们(额外)花费金钱。然而,在许多的实现中提供了存活定时器。&br&&br&一些服务器应用程序可能代表客户端占用资源,它们需要知道客户端主机是否崩溃。存活定时器可以为这些应用程序提供探测服务。Telnet服务器和Rlogin服务器的许多版本都默认提供存活选项。&br&&br&个人计算机用户使用TCP/IP协议通过Telnet登录一台主机,这是能够说明需要使用存活定时器的一个常用例子。如果某个用户在使用结束时只是关掉了电源,而没有注销(log off),那么他就留下了一个半打开(half-open)的连接。在图18.16,我们看到如何在一个半打开连接上通过发送数据,得到一个复位(reset)返回,但那是在客户端,是由客户端发送的数据。如果客户端消失,留给了服务器端半打开的连接,并且服务器又在等待客户端的数据,那么等待将永远持续下去。存活特征的目的就是在服务器端检测这种半打开连接。&br&&br&&b&二、keepalive如何工作?&/b&&br&&br&在此描述中,我们称使用存活选项的那一段为服务器,另一端为客户端。也可以在客户端设置该选项,且没有不允许这样做的理由,但通常设置在服务器。如果连接两端都需要探测对方是否消失,那么就可以在两端同时设置(比如NFS)。&br&&br&若在一个给定连接上,两小时之内无任何活动,服务器便向客户端发送一个探测段。(我们将在下面的例子中看到探测段的样子。)客户端主机必须是下列四种状态之一:&br&&br&1) 客户端主机依旧活跃(up)运行,并且从服务器可到达。从客户端TCP的正常响应,服务器知道对方仍然活跃。服务器的TCP为接下来的两小时复位存活定时器,如果在这两个小时到期之前,连接上发生应用程序的通信,则定时器重新为往下的两小时复位,并且接着交换数据。&br&&br&2) 客户端已经崩溃,或者已经关闭(down),或者正在重启过程中。在这两种情况下,它的TCP都不会响应。服务器没有收到对其发出探测的响应,并且在75秒之后超时。服务器将总共发送10个这样的探测,每个探测75秒。如果没有收到一个响应,它就认为客户端主机已经关闭并终止连接。&br&&br&3) 客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。&br&&br&4) 客户端主机活跃运行,但从服务器不可到达。这与状态2类似,因为TCP无法区别它们两个。它所能表明的仅是未收到对其探测的回复。&br&&br&服务器不必担心客户端主机被关闭然后重启的情况(这里指的是操作员执行的正常关闭,而不是主机的崩溃)。当系统被操作员关闭时,所有的应用程序进程(也就是客户端进程)都将被终止,客户端TCP会在连接上发送一个FIN。收到这个FIN后,服务器TCP向服务器进程报告一个文件结束,以允许服务器检测这种状态。&br&&br&在第一种状态下,服务器应用程序不知道存活探测是否发生。凡事都是由TCP层处理的,存活探测对应用程序透明,直到后面2,3,4三种状态发生。在这三种状态下,通过服务器的TCP,返回给服务器应用程序错误信息。(通常服务器向网络发出一个读请求,等待客户端的数据。如果存活特征返回一个错误信息,则将该信息作为读操作的返回值返回给服务器。)在状态2,错误信息类似于“连接超时”。状态3则为“连接被对方复位”。第四种状态看起来像连接超时,或者根据是否收到与该连接相关的ICMP错误信息,而可能返回其它的错误信息。&br&&br&具体来说:&br&&p&在TCP协议的机制里面,本身的心跳包机制,也就是TCP协议中的SO_KEEPALIVE,系统默认是设置2小时的心跳频率。需要用要用setsockopt将SOL_SOCKET.SO_KEEPALIVE设置为1才是打开,并且可以设置三个参数tcp_keepalive_time/tcp_keepalive_probes/tcp_keepalive_intvl,&/p&&p&分别表示连接闲置多久开始发keepalive的ACK包、发几个ACK包不回复才当对方死了、两个ACK包之间间隔多长。&/p&&br&&p&TCP协议会向对方发一个带有ACK标志的空数据包(KeepAlive探针),对方在收到ACK包以后,如果连接一切正常,应该回复一个ACK;如果连接出现错误了(例如对方重启了,连接状态丢失),则应当回复一个RST;如果对方没有回复,服务器每隔多少时间再发ACK,如果连续多个包都被无视了,说明连接被断开了。&/p&&br&&p&“心跳检测包”是属于TCP协议底层的检测机制,上位机软件只是解析显示网口的有用数据包,收到心跳包报文属于TCP协议层的数据,一般软件不会将它直接在应用层显示出来,所以看不到。以太网中的“心跳包”可以通过“以太网抓包软件”分析TCP/IP协议层的数据流看到。报文名称”TCP Keep-Alive”。&/p&&p&一些比较可靠的以太网转串口模块,都有心跳包的检测,比如致远电子的ZNE-100TL模块,配置“心跳包检测”间隔时间设为“10”秒,使用一款”wireshark”的抓包软件来实际查看下TCP/IP协议层“心跳包”数据。&/p&&br&&br&&br&&b&看了上面的内容,使用TCP自己的keep-alive机制,也是可以实现连接维持,通过TCP传输层的心跳包探知两端TCP连接的正确性,从而得知应用层的情况(TCP在,应用一定在,TCP不在了,应用一定不在了),但是这不是最优选择!&/b&&br&&br&&br&&b&那么既然有TCP的心跳机制,我们为什么还要在应用层实现自己的心跳检测机制呢?&/b&&br&评论中 &a data-hash=&0eaae55c34dd72dee76e& href=&///people/0eaae55c34dd72dee76e& class=&member_mention& data-editable=&true& data-title=&@Raynor& data-hovercard=&p$b$0eaae55c34dd72dee76e&&@Raynor&/a& 所说:&br&tcpip详解卷1有网络异常中断的3种情况,比如os回收端口的时候发送的rst包,如果os挂了就不会发这个消息了。 另外如果网络异常,有可能收到路由器返回的icmp主机不可达消息从而关闭连接。 keepalive之所以不靠谱,是因为需要从socket error获知连接断开,而且是被动断开。 而应用层自己实现的heartbeat可以自主检测超时,更方便修改超时时间和断开前处理。&br&以及@李乐所说: &br&keepalive设计初衷清除和回收死亡时间长的连接,不适合实时性高的场合,而且它会先要求连接一定时间内没有活动,周期长,这样其实已经断开很长一段时间,没有及时性。而且keepalive好像还不能主动通知应用层,需要主动调用api去检测异常。&br&&br&&b&二:使用自己应用层的心跳包,上述方法用于正常情况下的TCP连接维护,&/b&&br&&b&&br&
场景举例如下:在游戏服务器当中,内存中维护着众多玩家的在线数据,以方便调用,比如玩家的英雄队伍信息,玩家的世界位置信息,在玩家下线的时候,服务器必须知道并且清除掉数据(不然就会出现一个已经下线的玩家出现在世界上),在上述举例中,在服务器端收到TCP连接中断的消息后,调用close,期间可以通过socket得到玩家socket数据,从而获得user信息(如果提前根据ip或者mac做了记录)从而清除数据。&/b&&br&&b&
但是如果不是正常的玩家下线,TCP的四次握手没有成功,比如网络直接中断,client端的TCP协议的fin包没有发出去,服务端就不能及时探知玩家下线,如果依赖上面讲的TCP自己的keep alive探测机制,时间较长,做不到及时处理下线玩家,并且性能不佳,因为TCP/IP的设计者本身就不支持TCP的心跳,因为这可能因为中间网络的短暂中断导致两端良好的TCP连接断开。所以一般不启用TCP的心跳机制,那我们怎么处理这些异常下线呢?如果不处理,服务端将一直维护着TCP死连接,导致网络资源(一台服务器可以支持的TCP连接有限)和内存资源(内存中可能维护着该玩家的数据)的占用,所以就要用到应用层的心跳包了&br&&/b&&br&&p&&b&下面解释下应用层的心跳包:&/b&&/p&&br&&p&心跳包,通常是客户端每隔一小段时间向服务器发送的一个数据包,通知服务器自己仍然在线,服务器与客户端之间每隔一段时间 进行一次交互,来判断这个链接是否有效,并传输一些可能有必要的数据。通常是在建立了一个TCP的socket连接后无法保证这个连接是否持续有效,这时候两边应用会通过定时发送心跳包来保证连接是有效的。因按照一定的时间间隔发送,类似于心跳,所以叫做心跳包。事实上为了保持长连接(长连接指的是建立一次TCP连接之后,就认为连接有效,利用这个连接去不断传输数据,不断开TCP连接),至于包的内容,是没有特别规定的,不过一般都是很小的包,或者只是包含包头的一个空包。&/p&&img src=&/dc34df85d63caab_b.jpg& data-rawwidth=&480& data-rawheight=&361& class=&origin_image zh-lightbox-thumb& width=&480& data-original=&/dc34df85d63caab_r.jpg&&&br&&br&&br&&br&那么心跳包的意义就在于方便的在服务端管理客户端的在线情况,并且可以防止TCP的死连接问题,避免出现长时间不在线的死链接仍然出现在服务端的管理任务中。&br&&br&再举下面一个例子说明下为什么TCP自身的四次握手断开机制不能完全保证应用程序探知连接断开从而避免死连接。&br&&p&(1)做一个游戏客户端和服务器端的测试,手动强制关闭客户端进程,然后查看服务器的情况,结果往往是,服务器收到了客户端关闭的事件。其实, 这样会忽略一个问题,没有触发异常中断事件,比如网络中断。&/p&&br&&p&(2)手动关闭客户端进程,事实上并不能测试出想要的结果,因为进程是在应用层的,所以,这种测试方法不能保证网络驱动层也不发送数据报文给服务器。经过测试会发现,当应用层强制结束进程时,对于TCP连接,驱动层会发送reset数据包!而服务器收到这个数据包就可以正常关闭了!&/p&&br&&p&(3)那么,如果网络异常甚至直接拔掉网线呢,服务器收不到这个数据包,就会导致死连接存在!&/p&&br&&p&(4)所以,心跳包是必要的,或者使用TCP协议本身的Keep-alive来设置(但是keep-alive不够好)&/p&&br&&p& 我们不能误解TCP连接如同一条绳子,一方断开了,另外一方必然会知道的。事实上TCP连接,这个“面向连接”的物理连接并不存在,它只是抽象出来的概念,是一个虚拟的连接,对于物理层,对于网线、光纤而言,不存在连接不连接的概念,因为,对它们而言,无非就是一些电流脉冲而已,具体就是一个一个的IP报文。&/p&&br&&p& TCP的连接,不过是通过ACK、SEQ这些机制来模拟实现的,具体比如差错重传,拥塞控制&/p&&br&&br&&br&&p&那么心跳包的检测发送处理对服务器资源的耗费怎么判断?&/p&&p&这个要看心跳包发送的频率,我们可以自行设置&/p&&br&&p&另外这里有个例程,模拟了socket心跳包的C语言实现:&/p&&p&&a href=&///?target=http%3A//m.blog.csdn.net/blog/mfye1& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Socket心跳包异常检测的C语言实现,服务器与客户端代码案例&i class=&icon-external&&&/i&&/a&&/p&
游戏服务器常常有心跳包的设计。 我们的心跳包就是为了防止Socket断开连接,或是TCP的连接断开吗? 答案是否定的,TCP连接的通道是个虚拟的,连接的维持靠的是两端TCP软件对连接状态的维护。TCP 连接自身有维护连接的机制,说白了就是自身有长时间没有数据…
&p&朋友里有一位服务器大牛做过类似的总结,我征得他的同意把当时的资料分享一下。大量删节。&/p&&p&文章里说游戏服务器比较多,没怎么说web服务器。但是看了之后你就明白Web和游戏服务器在并发性方面根本性的不同了。&/p&&h2&&b&游戏服务器架构通识&/b&&/h2&&p&&b&· 前言&/b&&/p&&p&· 我们将从游戏服务器发展的简单历程出发,鸟瞰一下目前大多数的游戏服务器架构。&/p&&p&· 这里尽可能的避免陷入细节的技术问题,而是从技术进化的结果状态,反推原始问题是什么。希望能通过这个过程,解释清楚游戏服务器是在解决什么问题,痛点到底在哪里。&/p&&p&&b&· 一、早期网游服务器。&/b&&/p&&p&&br&&/p&&p&&br&&/p&&img src=&/v2-d0d7eed73b05dea8c621adcc_b.png& data-rawwidth=&1011& data-rawheight=&535& class=&origin_image zh-lightbox-thumb& width=&1011& data-original=&/v2-d0d7eed73b05dea8c621adcc_r.png&&&p&&br&&/p&&p&· 蛮荒时期的游戏服务器框架我们一笔带过,那时的游戏服务器和一个小Web服务没有区别。&/p&&p&· 蛮荒时代的服务器只负责存储玩家账号、数据、转发场景内其他玩家的行为。很多移动、使用技能等关键逻辑在服务器上根本没有。随意就能用变速齿轮改变游戏速度。&/p&&p&· 从传奇的时代开始,游戏服务器就不再是简单的上传存档、下载存档、访问页面而已。游戏服务器内部出现了游戏逻辑,既能用于同步每个玩家看到的世界,又能让逻辑与客户端分离,避免早期的网络游戏那种毫无防范的逻辑体系(对外挂防御能力为0)。&/p&&p&· 这种架构奇怪的地方是处理网络连接数据传输的压力和逻辑处理的压力在同一个服务器上(存储模块可能也在同一个进程),就算逻辑处理压力为0,承载人数也高不到哪去。&/p&&p&&b&· 二、早期游戏服务器的改进版本&/b&&/p&&p&· 当开发者们有了初步经验以后,新作品的开发,自然而然的过渡到了如下的形式:&/p&&p&&br&&/p&&img src=&/v2-4c91aeee6a9e00f8ef834a_b.png& data-rawwidth=&1112& data-rawheight=&680& class=&origin_image zh-lightbox-thumb& width=&1112& data-original=&/v2-4c91aeee6a9e00f8ef834a_r.png&&&p&&br&&/p&&p&· 游戏逻辑服务依然是在一台服务器上,单进程(逻辑处理本身肯定是在一个线程中,可以有子线程负责内网通信)。但是我们自然的想到,存储负载和网络连接负载可以从逻辑服上拆出来。&/p&&p&· 由于连接服务器本身没有时序性,很容易做分布式的(其实大部分游戏还是只用一个连接服),存储服务不要求高实时性,高峰期存盘间隔可以稍长一些,不会对游戏服造成影响。&/p&&p&·&b& 三、成熟形态的服务器框架(这节是重点)&/b&&/p&&p&· 1、逻辑服务器的负载均摊方法一:按照功能划分多个服务器进程&/p&&p&&br&&/p&&img src=&/v2-83d176b88c6ffcfba9d842_b.png& data-rawwidth=&1154& data-rawheight=&657& class=&origin_image zh-lightbox-thumb& width=&1154& data-original=&/v2-83d176b88c6ffcfba9d842_r.png&&&p&&br&&/p&&p&· 2、逻辑服务器的负载均摊方法二:按照场景划分多个服务器进程&/p&&p&&br&&/p&&img src=&/v2-3c87d414d04a2ab57c24f6bb78af5d45_b.png& data-rawwidth=&1139& data-rawheight=&654& class=&origin_image zh-lightbox-thumb& width=&1139& data-original=&/v2-3c87d414d04a2ab57c24f6bb78af5d45_r.png&&&p&&br&&/p&&p&· 难点在逻辑的设计上,要像做手术一样把本来是一体的功能切开,并抽象出若干个API来保持联系(服务器之间是TCP连接)。&/p&&p&· 在分解时,要找联系相对最薄弱的环节入手,比如场景和场景之间分开、单独抽出聊天服务、组队服务、好友服务。&/p&&p&· 无论如何分解,最终结果只能是有限个服务。而且分解的越细,开发难度就越大。因为跨服务器逻辑是把简单的同步逻辑变成了异步Callback逻辑,而且容易出现时序问题等不易测试的问题。&/p&&p&· 单个场景服务几乎是无法分解的。分解单个场景难度巨大以至于出现了BigWorld引擎来专门的解决场景分割问题,后面会谈到。&/p&&p&· 这种成熟形态的游戏服务器已经能满足现实中99%的频繁交互类网游需求,是大型MMO端游、页游的主流形式。&/p&&p&&b&· 对比Web服务器&/b&&/p&&p&大致只说一点:由于数据库的存在以及HTTP请求的特性,Web服务器天生就是并发的,也一直在高并发的路上越走越远。&/p&&img src=&/v2-e465c2a9d2_b.png& data-rawwidth=&1143& data-rawheight=&650& class=&origin_image zh-lightbox-thumb& width=&1143& data-original=&/v2-e465c2a9d2_r.png&&&p&&br&&/p&&p&&b&· 附:开房间式的网络游戏&/b&&/p&&p&&br&&/p&&p&· 开房间式的网络游戏也是游戏的一个重要分支,英雄联盟、DOTA、很多手游例如皇室战争、王者荣耀等等。&/p&&p&· 这种游戏房间之间几乎没有交互,只有大厅内有交互,可以理解为原始形态的游戏服务器的平行扩展。&/p&&p&· 房间式游戏扩展难度较小,只是需要根据玩家数量动态扩展游戏房间的数量、服务器数量。很像网站的架构。&/p&&p&· 这种游戏架构最最适合放在云平台上,设计合理的话,它可能遇到的问题和大型网站几乎一模一样。不需要特别的讨论它们。&/p&&p&· 只是,毕竟游戏不都是开房间的玩法。&/p&&h2&&b&· 小结:游戏服务器框架特点&/b&&/h2&&p&· 1、真正的数据都在内存中,数据库性能不那么重要&/p&&p&· 注:很多大型游戏采用了共享内存,避免宕机时损失过大。&/p&&p&· 2、单CPU性能比CPU数量重要的多。&/p&&p&· 3、目前有很多游戏,特别是手游,使用Redis读写代替内存读写,甚至也有用Mongo的。&/p&&p&· 4、开新服、旧区合服的情况,非常适合云平台。&/p&&p&&b&· 先进服务器框架&/b&&/p&&p&1、BigWorld。理念过于超前,把并发性做到极致,开发友好度弱到极致,已废。&/p&&p&2、Skynet。本人强烈推荐,谁学谁知道,除了必须要用lua语言,没有什么缺点。&/p&&p&&br&&/p&&p&&b&· 聊聊十万行代码&/b&&/p&&p&· 游戏服务器开发速度受美术资源制作速度、客户端开发速度制约。近几年我猜测服务器方面并不会有大的技术革新。&/p&&p&· 游戏开发未来的趋势是多元化、低门槛化、大众化。很长一段时间内BigWorld这种大怪兽级别的引擎不会再崛起。&/p&&p&· 分布式框架的崛起时间点,无论如何,也在VR技术成熟之后了。&/p&&p&游戏开发上有任何疑问,均可私聊咱进行交流,或访问咱的主页&a href=&///?target=http%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&皮皮关游戏开发教育&i class=&icon-external&&&/i&&/a&&/p&
朋友里有一位服务器大牛做过类似的总结,我征得他的同意把当时的资料分享一下。大量删节。文章里说游戏服务器比较多,没怎么说web服务器。但是看了之后你就明白Web和游戏服务器在并发性方面根本性的不同了。游戏服务器架构通识· 前言· 我们将从游戏服务器…
从2013年起,经朋友推荐开始用Golang编写游戏登陆服务器, 配合C++做第三方平台验证. 到编写独立工具导表工具&a href=&///?target=https%3A///davyxu/tabtoy& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GitHub - davyxu/tabtoy: 跨平台的高性能便捷电子表格导出器&i class=&icon-external&&&/i&&/a&. 以及网络库&a href=&///?target=https%3A///davyxu/cellnet& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GitHub - davyxu/cellnet: 简单,方便,高效的Go语言的游戏服务器底层&i class=&icon-external&&&/i&&/a&. 最终使用这些工具及库编写整个游戏服务器框架, 我的感受是很不错的&br&&br&细节看来, 有如下的几个点:&br&&br&&b&语言, 库&/b&&br&Golang语言特性和C很像, 简单, 一张A4纸就能写完所有特性. 你想想看, C++到了领悟阶段, 也只用那几个简单特性, 剩下的都是一大堆解决各种内存问题的技巧. 而Golang一开始就简单, 何必浪费生命去研究那一大堆的奇技淫巧呢?&br&&br&Golang的坑只有2个:1. interface{}和nil配合使用,
2. for循环时, 将循环变量引入闭包(&a href=&///?target=http%3A///sunicdavy/archive//211895.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Golang, Lua, C#闭包变量捕获差异&i class=&icon-external&&&/i&&/a&)
完全不影响正常使用, 复合语言概念, 只是看官方后面怎么有效的避免&br&&br&用Golang就忘记继承那套东西, 用组合+接口&br&&br&用Golang服务器如何保证解决游戏服务器存盘一致性问题? stop the world是肯定的, 但是Golang可以从语言层并发序列化玩家数据, 再通过后台存盘&br&&br&channel是goroutine虽然是Golang的语言特性. 但是在编写服务器时, 其实只有底层用的比较多.&br&&br&Golang的第三方库简直多如牛毛, 好的也很多&br&&br&不要说模板了, C#的也不好用, 官方在纠结也不要加, 使用中, 没模板确实有点不方便. 用interface{}/反射做泛型对于Golang这种强类型语言来说,还是有点打脸&br&&br&&br&&b&运行期&/b&&br&Golang和C++比性能的话, 这是C++的优势, Golang因为没虚拟机, 只有薄薄的一层调度层. 因此性能是非常高的, 用一点性能牺牲换开发效率, 妥妥的&br&&br&1.6版后的GC优化的已经很好了, 如果你不是高性能,高并发Web应用, 非要找出一堆的优化技巧的话. 只用Golang写点游戏服务器, 那点GC损耗可以忽略不计&br&&br&和其他现代语言一样, 崩溃捕捉是标配功能, 我用Golang的服务器线上跑, 基本没碰到过崩溃情况&br&&br&热更新: 官方已经有plugin系统的提交, 跨平台的. 估计很快就可以告别手动cgo做so热更新&br&&br&&b&开发, 调试, 部署, 优化&/b&&br&LiteIDE是我首选的Golang的IDE, 虽然有童鞋说B格不高. 但这估计实在是找不到缺点说了, 别跟我说Visual Studio, 那是宇宙级的...&br&&br&曾经听说有人不看好Golang, 我问为啥: 说这么新的语言, 不好招人,后面打听到他是个策划... 好吧&br&真实情况是这样的: Golang对于有点编程基础的新人来说, 1周左右可以开始贡献代码. 老司机2~3天. &br&&br&开发效率还是不错的,
一般大的游戏功能, 2*2人一周3~4个整完. 这换C++时代, 大概也就1~2个还写不完. 对接服务器sdk的话, 大概1天接个10多个没问题&br&&br&Golang自带性能调优工具, 从内存, CPU, 阻塞点等几个方面直接出图进行分析, 非常直观, 可以参考我博客几年前的分析: &a href=&///?target=http%3A///sunicdavy/archive//210308.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&使用Golang进行性能分析(Profiling)&i class=&icon-external&&&/i&&/a&&br&&br&Golang支持交叉编译, 跨平台部署, 什么概念? linux是吧? 不问你什么版本, 直接windows上编译输出一个elf, 甩到服务器上开跑.不超过1分钟时间..&br&&br&现在北上广深蓉很多新开项目都选择了Golang,就是看到Golang的开发效率和招人上面&br&对于Golang开发还有纠结可以看下我的博文:&a href=&///?target=http%3A///sunicdavy/archive//212611.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&服务器开发语言比较&i class=&icon-external&&&/i&&/a&&br&&br&&br&&br&打个广告, 各种开发笔记, 参考 &a href=&///?target=http%3A///sunicdavy& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&战魂小筑 - C++博客&i class=&icon-external&&&/i&&/a&&br&&br&开源服务器网络库, &a href=&///?target=https%3A///davyxu/cellnet& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/davyxu/celln&/span&&span class=&invisible&&et&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a& 欢迎赏星&br&&br&多个商用项目使用的 &a href=&///?target=https%3A///davyxu/tabtoy& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GitHub - davyxu/tabtoy: 跨平台的高性能便捷电子表格导出器&i class=&icon-external&&&/i&&/a&
从2013年起,经朋友推荐开始用Golang编写游戏登陆服务器, 配合C++做第三方平台验证. 到编写独立工具导表工具. 以及网络库. 最终…
通常来讲,就是利用 select 的空余时间,来进行时钟检查,不管是 select / poll / epoll/ kevent,以下统称 select,它有一个等待时间作为参数,即没有事件时,最多 wait 多少时间,我们把这个作为网络库的基准频率,比如 10MS,或者 20MS, 25MS, 50MS,都是常用的几个值。&br&&br&就是说网络库调用 select 等待事件时如果没有事件,那么最长等待 10MS 就返回了,这时再处理完所有网络事件后,就可以来处理时钟数据了。事件处理函数就是这样:&br&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&k&&def&/span& &span class=&nf&&update_events&/span&&span class=&p&&(&/span&&span class=&n&&milisec&/span& &span class=&o&&=&/span& &span class=&mi&&10&/span&&span class=&p&&):&/span&
&span class=&n&&result&/span& &span class=&o&&=&/span& &span class=&n&&selector&/span&&span class=&o&&.&/span&&span class=&n&&select&/span&&span class=&p&&(&/span&&span class=&n&&milisec&/span&&span class=&p&&)&/span&
&span class=&k&&for&/span& &span class=&n&&fd&/span&&span class=&p&&,&/span& &span class=&n&&event&/span& &span class=&ow&&in&/span& &span class=&n&&result&/span&&span class=&p&&:&/span&
&span class=&n&&do&/span& &span class=&n&&something&/span& &span class=&k&&with&/span& &span class=&n&&socket&/span& &span class=&n&&event&/span&
&span class=&n&&current&/span& &span class=&o&&=&/span& &span class=&n&&time&/span&&span class=&o&&.&/span&&span class=&n&&time&/span&&span class=&p&&()&/span&
&span class=&n&&update_timer&/span&&span class=&p&&(&/span&&span class=&n&&current&/span&&span class=&p&&)&/span&
&span class=&k&&while&/span& &span class=&mi&&1&/span&&span class=&p&&:&/span&
&span class=&n&&WAIT_MILLISEC&/span& &span class=&o&&=&/span& &span class=&mi&&10&/span&
&span class=&n&&update_events&/span&&span class=&p&&(&/span&&span class=&n&&WAIT_MILLISEC&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&&br&关键就是这个两次 select 中间 update_timer 的任务:集合中检查需要唤醒的时钟,并且调用它们的回调函数,来驱动整个服务器的时钟运行,以最简单的扫描法为例:&br&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&k&&def&/span& &span class=&nf&&update_timer&/span& &span class=&p&&(&/span&&span class=&n&&current&/span&&span class=&p&&):&/span&
&span class=&k&&for&/span& &span class=&n&&timer&/span& &span class=&ow&&in&/span& &span class=&n&&available_timers&/span&&span class=&p&&:&/span&
&span class=&k&&while&/span& &span class=&n&&current&/span& &span class=&o&&&=&/span& &span class=&n&&timer&/span&&span class=&o&&.&/span&&span class=&n&&expires&/span&&span class=&p&&:&/span&
&span class=&n&&timer&/span&&span class=&o&&.&/span&&span class=&n&&callback&/span&&span class=&p&&(&/span&&span class=&n&&current&/span&&span class=&p&&)&/span&
&span class=&n&&timer&/span&&span class=&o&&.&/span&&span class=&n&&expires&/span& &span class=&o&&+=&/span& &span class=&n&&timer&/span&&span class=&o&&.&/span&&span class=&n&&period&/span&
&/code&&/pre&&/div&&br&available_timers 记录着当前可用的所有 timer 的集合,expires 是他们需要被触发的时间,如果当前时间大于等于这个 expires,认为该 timer 需要被触发到。注意 timer.expires 更新的时候是 += 周期,而不是 = current + 周期,后者会导致误差积累,长时间运行后偏差越来越大。同时这里需要 while,因为可能跨越两个以上周期,当然只运行一次的 timer 就不需要了,这里只是简化下。&br&&br&比如 libevent 里面的主循环 event_base_loop 每次 select 完毕后就调用一次 timeout_process。&br&&br&这就是 Timer 调度的基本原理。&br&&br&可能你会发现每次 select 结束都要扫描整个 available_timers 集合,是一个非常费时间的事情,那么首先想到的就是优先队列了:将 Timer 节点按照 expires 的先后顺序,将最快要发生的超时节点放在前面,每次检测队列头就可以判断是否超时了。&br&&br&比如 libevent 里面的 timerout_process 函数,就是用最小堆来存储超时事件,每次检测堆的第一个节点如果超时则删除并继续检测下一个,否则跳出循环(此时没有任何节点到期)。&br&&br&还有一种固定超时队列,就是里面的节点的超时周期都是相同的,那么每次增加都在最后,每次检测都只检测头部。比如所有链接都要检测60秒无事件超时这个事情,就可以用它,因为60秒是固定的,新增时放到队列最后,检测时只检测头部是否超时,如果有事件来到,就删除并重新加入队列末尾,这是固定超时队列。&br&&br&还有上面专门说的系统提供的 timerfd,创建后加入
select, 但是受限于 linux 系统,跨平台就用不了了,不能太依赖。&br&&br&然而这些都不算最完美的解决方案,一旦超时节点多达上万个,每个时间都不同,又考虑通用实现(非特定平台实现)的话,这几种调度方式是要吃亏的,目前最好的算法是 Linux Kernel 的时间轮算法,几乎保证不管有多少个时钟对象要处理,每次 update_timer 的时间都几乎是常数。&br&&br&具体可以看代码 kernel/timer.c ,楼上提到 skynet 里面有个时间轮的应用层实现,看了一眼,和 skynet 依赖太大了,需要自己做剥离工作,同时全局变量做 tvec_base(时钟管理器),这有点要命,你多线程每个线程一个 selector 的时候,就没法用了。更加具备简单性和可拆分性的 Linux 时间轮的应用层实现见我写的:&br&&a href=&///?target=https%3A///skywind3000/AsyncNet/blob/master/system/itimer.h& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AsyncNet/itimer.h at master · skywind3000/AsyncNet · GitHub&i class=&icon-external&&&/i&&/a&&br&&a href=&///?target=https%3A///skywind3000/AsyncNet/blob/master/system/itimer.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AsyncNet/itimer.c at master · skywind3000/AsyncNet · GitHub&i class=&icon-external&&&/i&&/a&&br&&br&两个文件,拷贝走就得了,没有任何第三个文件的依赖,更没有全局唯一的时钟管理器。&br&&br&一般来讲 “侵入式” 的网络库,都会把这个事情给管理了,因为他们接管你的主循环,比如 libevent, skynet 之类,你进入一个 event_loop 就只有程序结束才能出来了,而非侵入式的网络库不会接管你的主循环,可以让你自己主动去触发。&br&&br&比如某同事想在 libevent 上跑一套自己的时钟系统,而 event_base_dispatch 属于进去就出不来的 “侵入式” 设计。为了能在 select 前后加入自己的时钟调度,不得不把 libevent 改出一个 branch 来,所以 侵入式 是比较恶劣的设计。libevent 应该学习一下 win32,把 GetMessage, DispatchMessage 放出来,比如叫做:&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&kt&&int&/span& &span class=&nf&&event_base_update&/span&&span class=&p&&(&/span&&span class=&k&&struct&/span& &span class=&n&&event_base&/span& &span class=&o&&*&/span&&span class=&n&&base&/span&&span class=&p&&,&/span& &span class=&kt&&int&/span& &span class=&n&&max_wait_time&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&br&让你主动去触发它,然后灵活的在前后做点事情,还可以用 event_base_wakeup 从另外一个现成唤醒它,这样就会灵活很多了,完全取代
event_base_dispatch 这个进去出不来的死循环。&br&&br&每次 select wait 的时间一般用一个固定值,称为一个 TICK,固定值选大了,时钟基准周期就会很长,短时误差就会增大,选小了,又会占用额外 cpu,可以模拟 Linux 使用 100Hz的值,即 10ms来做,这也是通行做法。&br&&br&不嫌麻烦还可以每次从 timer 集合里面选择最先要超时的事件,计算还有多长时间就会超时,作为 select wait 的值,每次都不一样,每次都基本精确,同时不会占用多余 cpu,这叫 tickless,Linux 的 3.x以上版本也支持 tickless 的模式来驱动各种系统级时钟,号称更省电更精确,不过需要你手动打开,FreeBSD 9 以后也引入了 tickless。&br&&br&TICKLESS 模式可以说是一个新的方向,但是目前处于默认关闭的测试状态,那么你的网络库到底是用 TICK 还是 TICKLESS,看你根据具体情况来评估了。
通常来讲,就是利用 select 的空余时间,来进行时钟检查,不管是 select / poll / epoll/ kevent,以下统称 select,它有一个等待时间作为参数,即没有事件时,最多 wait 多少时间,我们把这个作为网络库的基准频率,比如 10MS,或者 20MS, 25MS, 50MS,都…
你说的魔兽争霸3是出了冰封王座资料片的暴雪那个魔兽争霸3吗?&br&&br&它的同步机制我还稍微了解一点,但没有详细了解,凭记忆稍微说点吧。(如有错误请指正,因为我也想学习。)&br&&br&&br&&a data-hash=&ecc0ec035f& href=&///people/ecc0ec035f& class=&member_mention& data-editable=&true& data-title=&@vczh& data-hovercard=&p$b$ecc0ec035f&&@vczh&/a& 可能玩魔兽玩得少。其实魔兽争霸3的同步很有意思。说到联网,必然有一台服务器(主机)。而游戏中的种种情况,例如技能判定,野怪行为,电脑AI行为,诸如此类种种,服务器皆不管,全部交由客户端自行判定。 &br&&br&因此这点上和一般的网游不一样呢。&br&&br&那主机干吗呢?实际上主机只干一个简单的事情。&br&&br&当某玩家A用鼠标选中一个单位,让他放个技能后。客户端A首先会向服务器发一条信息,说,我要放技能吧。服务器接到这条信息后,会给这个信息定一个时间,如某年某月某日某分某秒,客户端A放了个技能。然后把这个结果(包括时间戳)广播给所有客户端。于是所有客户端受到了这条广播(包括最初向服务器发我要放技能这条信息的客户端A)。&br&&br&于是所有的客户端根据服务器广播的信息,&b&分别判定和计算客户端A的玩家在特定时间放这个技能可能带来的影响&/b&。&br&&br&特别的,在此过程中,服务器根本不进行判定,服务器甚至根本都不知道这局游戏的地图长什么样子,地图上有多少个兵。服务器只做两件事情:广播客户端提交的消息,以及为消息加一个统一的时间戳。&br&&br&那么既然服务器判定都不判定,那么同一局游戏为每一个客户端都可能看到相同的场景呢?(如果你在A的电脑上看到剑圣在开大转圈圈,那么在B的电脑、C的电脑上看到的也是剑圣在开大转圈圈。)&br&&br&因为每一局游戏开始的情况是相同的。大家都进同一张地图,而地图都是一样的。因此一局游戏的&b&初始条件&/b&在每一个客户端上都是一样的。&br&&br&而每一个玩家的操作,都会被服务器统一加一个时间戳并广播给所有客户端。因此在每一个客户端上,任何玩家(包括其他其他客户端表示的玩家)的任何&b&操作&/b&也是一样的。&br&&br&因为大家都是玩同一个游戏,所以技能的判定、野怪(或电脑)AI的&b&判定&/b&也是一样的。&br&&br&因此,可以明确:对于每一客户端,&b&初始条件&/b&相同 -& &b&操作&/b&相同 -& &b&判定&/b&相同 -& &b&结果&/b&也相同。于是乎,你让你的剑圣开大转圈圈,所有人的屏幕上都会看到你的剑圣在开大转圈圈。但在此过程中,服务器甚至不需要知道“剑圣“是什么,他的大招是什么。服务器只负责把所有客户端传上来的消息盖个统一的时间戳再广播出去就行了。&br&&br&当然还有一点值得一说:游戏中是有随机判定的,怎么保证所有客户端的随机判定结果都一致呢?(如果不一致会很麻烦,你的屏幕中剑圣跳劈劈死敌人了,而他屏幕中剑圣只是普通A下被反杀,同一局游戏同一个剑圣在一个人看来杀死了敌人,另一个人看来已经死了。此时剑圣处于量子叠加态,堪比薛定谔的猫了。)&br&&br&其实在游戏开始时,服务器给每一个客户端分一个&b&统一的随机数种子&/b&就行了。&br&&br&OK,所有问题解决。大家终于可以愉快的开一局魔兽3了。不过事先声明一下,以上结论都是我的推测,我也没听过暴雪官方有说过什么。&b&如果有谁有权威的解释,方便的话在答案中AT下我咯&/b&。&br&&br&另外,这么做不怕客户端作弊吗?嗯,得分什么作弊。如果是开图这种绅士作弊行为,这种机制是避免不了的。但如果是无敌、秒杀、改钱这种流氓作弊行为,这种机制根本不怕。&br&&br&因为无敌、秒杀、改钱必须有个合理的理由,例如喝无敌药水,提升攻击秒杀等。平白无故的无敌和秒杀敌人的消息,你要怎么发给服务器,而且令服务器乖乖广播给其他客户端?换句话说,如果客户端外挂令玩家可以平白无故无敌秒杀,那么会发生什么。&br&&br&想象一下这么一个场景,一个剑圣平白无故暴击率100%攻击力1W,于是到处跳劈杀得敌人屁滚尿流。于是作弊玩家看着心里那个爽。但是剑圣平白无故变强这一结果无法广播给其他客户端,于是其他客户端那里,剑圣只是个战五渣还到处追人砍,然后一遍一遍被别人秒。&br&&br&于是作弊玩家更像是陷入了某种幻境中,以为自己叼炸天,其实自己就一战五渣。被别人血虐自己还不知道呢。&br&&br&(不过实际情况开这种外挂的玩家早因为数据不同步弹出游戏了。)&br&&br&顺便提点推论的依据:&br&&br&1、dota不可以断线重连,LOL可以。&br&2、魔兽3的录像只能快进,不能跳过。&br&3、魔兽3外怪可以开地图,即看见隐身单位和视野之外的单位,但不允许攻击。&br&&br&------------少许补充-------------- &br&&a data-hash=&db853b63f77d5f32fbb567& href=&///people/db853b63f77d5f32fbb567& class=&member_mention& data-editable=&true& data-title=&@钟三鸣& data-hovercard=&p$b$db853b63f77d5f32fbb567&&@钟三鸣&/a& 提及了GetLocalPlayer函数,这是暴雪开发的脚本语言JASS可调用的一个函数。这个函数的存在以及利用这个函数可以实现的一些有趣效果,可以作为我以上言论的铁证。不过这里涉及到魔兽地图制作的一些知识,以后有机会展开讨论吧。&br&&img src=&/f6c9ba0e571af9b2f1a5ec459e6f2d18_b.png& data-rawwidth=&1536& data-rawheight=&900& class=&origin_image zh-lightbox-thumb& width=&1536& data-original=&/f6c9ba0e571af9b2f1a5ec459e6f2d18_r.png&&
你说的魔兽争霸3是出了冰封王座资料片的暴雪那个魔兽争霸3吗? 它的同步机制我还稍微了解一点,但没有详细了解,凭记忆稍微说点吧。(如有错误请指正,因为我也想学习。)
可能玩魔兽玩得少。其实魔兽争霸3的同步很有意思。说到联网,必然有一台服务…
完全不可取,对你来说,游戏服务器其实和你说的外包掏粪没区别,也是掏粪。&br&&br&你这个病,我 3 年前说过,你可以翻翻老答案,叫深山修炼情结,大概是中国人特有的精神气质……&br&&br&具体症状就是。。。。(这里不谈疗法,疗法自寻老答案)&br&我学好 vim 就天下无敌啦&br&我闭关啃完英文的 emacs 教程和几本书就内力升级啦&br&我艰苦岁月不玩游戏搞一遍 lfs 就精通 linux 啦&br&我努力精通 go 就可以站在时代之巅啦&br&听说写完算法导论习题可以炼成乾坤大挪移啦&br&我要清心寡欲苦学不辍就可以当精通底层的天才少年啦&br&我学会 php 就可以跟扎克一样创业做网站成年轻首富啦&br&。。。。。&br&我一辈子钻研游戏服务器肯定会修炼成不世神功,到时候肯定小手一抖,飞沙走石,谈笑之间,代码自动跪好,真是太爽啦&br&&br&什么你怀疑我?凭什么怀疑别人的梦想,我门口埋了雷,是真正下定了决心的,你根本想象不到我的意志有多强大,信念有多坚定。只不过是既得利益者对新人的打击,害怕别人分蛋糕,巴拉巴拉拉巴拉巴巴巴拉拉拉拉巴巴。。。&br&我不敢怀疑……只是你拿的是本山寨如来神掌而已啊……&br&&br&&br&懂了么?&br&重点永远不在方向,你干了 3 年外包是外包狗,那干 3 年游戏服务器也不会例外。&br&4399,听过吗?&br&&br&你要嫌这些个公司烂的话……网易,盛大,金山,完美世界,听过吗?
完全不可取,对你来说,游戏服务器其实和你说的外包掏粪没区别,也是掏粪。 你这个病,我 3 年前说过,你可以翻翻老答案,叫深山修炼情结,大概是中国人特有的精神气质…… 具体症状就是。。。。(这里不谈疗法,疗法自寻老答案) 我学好 vim 就天下无敌啦 …
&p&一般的网站应用程序,是典型的Request-Response模式,通过tcp和服务器建立一次链接,而请求数据和影响数据通过http协议进行组装,当完成一次交互的时候,服务器端和客户端tcp链接就会释放,把服务器端socket资源留给新的客户端。通常web程序是比较好扩展的,通过硬件负载均衡和添加web服务器来实现,这一套方案业界都已经比较成熟了。网游比较特殊,最大的特点在于客户端和服务器端是要进行长连接的,客户端和服务器端基本上一直要保持连接,不是典型的Request-Response模式,Client会主动给Server发送数据,Server也可能主动往Client发送数据,生命周期比较长,一次发送的数据量比较小,但是数据交互发送比较频繁。由于要进行长连接,服务器端的socket就不能进行复用,单台服务器处理请求是会有限。用web的方案解决扩展问题,也不太适用。在web程序中,客户端之间的数据是没有交互的,所有的数据都是通过web服务器响应给客户端,但是网游服务器中,每个客户端的数据的变化,都要通过服务器端广播给其他客户端。所以客户端会有上限,这也就是为什么服务器要进行分区,一个区里面同时在线人数会有限制。&/p&
一般的网站应用程序,是典型的Request-Response模式,通过tcp和服务器建立一次链接,而请求数据和影响数据通过http协议进行组装,当完成一次交互的时候,服务器端和客户端tcp链接就会释放,把服务器端socket资源留给新的客户端。通常web程序是比较好扩展的…
你ssh连上服务器的情况下,拔掉网线,你任然检测不到网络断开了(没有FIN),这时候把网线插回去,敲两下键盘,终端一样有反应,链接还活着。因为tcp的链接是否有效,依赖链接两端的状态确定,你在你机器上拔掉网线,你是知道这件事情的,但是中间网络设备拔掉网线,或者出现什么问题,你完全无法得知链接是否还有效,依赖tcp本身的keepalive机制需要半个小时以上才能检测得出来。&br&&br&所幸,keepalive如今也可以设置检测时间了:&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&cm&&/* ikeepalive: tcp keep alive option */&/span&
&span class=&kt&&int&/span& &span class=&nf&&ikeepalive&/span&&span class=&p&&(&/span&&span class=&kt&&int&/span& &span class=&n&&sock&/span&&span class=&p&&,&/span& &span class=&kt&&int&/span& &span class=&n&&keepcnt&/span&&span class=&p&&,&/span& &span class=&kt&&int&/span& &span class=&n&&keepidle&/span&&span class=&p&&,&/span& &span class=&kt&&int&/span& &span class=&n&&keepintvl&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&enable&/span& &span class=&o&&=&/span& &span class=&p&&(&/span&&span class=&n&&keepcnt&/span& &span class=&o&&&&/span& &span class=&mi&&0&/span& &span class=&o&&||&/span& &span class=&n&&keepidle&/span& &span class=&o&&&&/span& &span class=&mi&&0&/span& &span class=&o&&||&/span& &span class=&n&&keepintvl&/span& &span class=&o&&&&/span& &span class=&mi&&0&/span&&span class=&p&&)&/span&&span class=&o&&?&/span& &span class=&mi&&0&/span& &span class=&o&&:&/span& &span class=&mi&&1&/span&&span class=&p&&;&/span&
&span class=&kt&&unsigned&/span& &span class=&kt&&long&/span& &span class=&n&&value&/span&&span class=&p&&;&/span&
&span class=&cp&&#if (defined(WIN32) || defined(_WIN32) || defined(_WIN64) || defined(WIN64))&/span&
&span class=&cp&&#define _SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR, 4)&/span&
&span class=&kt&&unsigned&/span& &span class=&kt&&long&/span& &span class=&n&&keepalive&/span&&span class=&p&&[&/span&&span class=&mi&&3&/span&&span class=&p&&],&/span& &span class=&n&&oldkeep&/span&&span class=&p&&[&/span&&span class=&mi&&3&/span&&span class=&p&&];&/span&
&span class=&n&&OSVERSIONINFO&/span& &span class=&n&&info&/span&&span class=&p&&;&/span&
&span class=&kt&&int&/span& &span class=&n&&candoit&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&n&&info&/span&&span class=&p&&.&/span&&span class=&n&&dwOSVersionInfoSize&/span& &span class=&o&&=&/span& &span class=&k&&sizeof&/span&&span class=&p&&(&/span&&span class=&n&&info&/span&&span class=&p&&);&/span&
&span class=&n&&GetVersionEx&/span&&span class=&p&&(&/span&&span class=&o&&&&/span&&span class=&n&&info&/span&&span class=&p&&);&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&info&/span&&span class=&p&&.&/span&&span class=&n&&dwPlatformId&/span& &span class=&o&&==&/span& &span class=&n&&VER_PLATFORM_WIN32_NT&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&((&/span&&span class=&n&&info&/span&&span class=&p&&.&/span&&span class=&n&&dwMajorVersion&/span& &span class=&o&&==&/span& &span class=&mi&&5&/span& &span class=&o&&&&&/span& &span class=&n&&info&/span&&span class=&p&&.&/span&&span class=&n&&dwMinorVersion&/span& &span class=&o&&&=&/span& &span class=&mi&&1&/span&&span class=&p&&)&/span& &span class=&o&&||&/span&
&span class=&p&&(&/span&&span class=&n&&info&/span&&span class=&p&&.&/span&&span class=&n&&dwMajorVersion&/span& &span class=&o&&&=&/span& &span class=&mi&&6&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&n&&candoit&/span& &span class=&o&&=&/span& &span class=&mi&&1&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&n&&value&/span& &span class=&o&&=&/span& &span class=&n&&enable&/span&&span class=&o&&?&/span& &span class=&mi&&1&/span& &span class=&o&&:&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&n&&isetsockopt&/span&&span class=&p&&(&/span&&span class=&n&&sock&/span&&span class=&p&&,&/span& &span class=&n&&SOL_SOCKET&/span&&span class=&p&&,&/span& &span class=&n&&SO_KEEPALIVE&/span&&span class=&p&&,&/span& &span class=&p&&(&/span&&span class=&kt&&char&/span&&span class=&o&&*&/span&&span class=&p&&)&/span&&span class=&o&&&&/span&&span class=&n&&value&/span&&span class=&p&&,&/span&
&span class=&k&&sizeof&/span&&span class=&p&&(&/span&&span class=&n&&value&/span&&span class=&p&&));&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&candoit&/span& &span class=&o&&&&&/span& &span class=&n&&enable&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&ret&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&n&&keepalive&/span&&span class=&p&&[&/span&&span class=&mi&&0&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&enable&/span&&span class=&o&&?&/span& &span class=&mi&&1&/span& &span class=&o&&:&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&n&&keepalive&/span&&span class=&p&&[&/span&&span class=&mi&&1&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&p&&((&/span&&span class=&kt&&unsigned&/span& &span class=&kt&&long&/span&&span class=&p&&)&/span&&span class=&n&&keepidle&/span&&span class=&p&&)&/span& &span class=&o&&*&/span& &span class=&mi&&1000&/span&&span class=&p&&;&/span&
&span class=&n&&keepalive&/span&&span class=&p&&[&/span&&span class=&mi&&2&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&p&&((&/span&&span class=&kt&&unsigned&/span& &span class=&kt&&long&/span&&span class=&p&&)&/span&&span class=&n&&keepintvl&/span&&span class=&p&&)&/span& &span class=&o&&*&/span& &span class=&mi&&1000&/span&&span class=&p&&;&/span&
&span class=&n&&ret&/span& &span class=&o&&=&/span& &span class=&n&&WSAIoctl&/span&&span class=&p&&((&/span&&span class=&kt&&unsigned&/span& &span class=&kt&&int&/span&&span class=&p&&)&/span&&span class=&n&&sock&/span&&span class=&p&&,&/span& &span class=&n&&_SIO_KEEPALIVE_VALS&/span&&span class=&p&&,&/span&
&span class=&p&&(&/span&&span class=&n&&LPVOID&/span&&span class=&p&&)&/span&&span class=&n&&keepalive&/span&&span class=&p&&,&/span& &span class=&mi&&12&/span&&span class=&p&&,&/span& &span class=&p&&(&/span&&span class=&n&&LPVOID&/span&&span class=&p&&)&/span&&span class=&n&&oldkeep&/span&&span class=&p&&,&/span& &span class=&mi&&12&/span&&span class=&p&&,&/span& &span class=&o&&&&/span&&span class=&n&&value&/span&&span class=&p&&,&/span& &span class=&nb&&NULL&/span&&span class=&p&&,&/span& &span class=&nb&&NULL&/span&&span class=&p&&);&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&ret&/span& &span class=&o&&==&/span& &span class=&n&&SOCKET_ERROR&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&o&&-&/span&&span class=&mi&&2&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&cp&&#elif defined(SOL_TCL) && defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL)&/span&
&span class=&n&&value&/span& &span class=&o&&=&/span& &span class=&n&&enable&/span&&span class=&o&&?&/span& &span class=&mi&&1&/span& &span class=&o&&:&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&n&&isetsockopt&/span&&span class=&p&&(&/span&&span class=&n&&sock&/span&&span class=&p&&,&/span& &span class=&n&&SOL_SOCKET&/span&&span class=&p&&,&/span& &span class=&n&&SO_KEEPALIVE&/span&&span class=&p&&,&/span& &span class=&p&&(&/span&&span class=&kt&&char&/span&&span class=&o&&*&/span&&span class=&p&&)&/span&&span class=&o&&&&/span&&span class=&n&&value&/span&&span class=&p&&,&/span& &span class=&k&&sizeof&/span&&span class=&p&&(&/span&&span class=&kt&&long&/span&&span class=&p&&));&/span&
&span class=&n&&value&/span& &span class=&o&&=&/span& &span class=&n&&keepcnt&/span&&span class=&p&&;&/span&
&span class=&n&&isetsockopt&/span&&span class=&p&&(&/span&&span class=&n&&sock&/span&&span class=&p&&,&/span& &span class=&n&&SOL_TCP&/span&&span class=&p&&,&/span& &span class=&n&&TCP_KEEPCNT&/span&&span class=&p&&,&/span& &span class=&p&&(&/span&&span class=&kt&&char&/span&&span class=&o&&*&/span&&span class=&p&&)&/span&&span class=&o&&&&/span&&span class=&n&&value&/span&&span class=&p&&,&/span& &span class=&k&&sizeof&/span&&span class=&p&&(&/span&&span class=&kt&&long&/span&&span class=&p&&));&/span&
&span class=&n&&value&/span& &span class=&o&&=&/span& &span class=&n&&keepidle&/span&&span class=&p&&;&/span&
&span class=&n&&isetsockopt&/span&&span class=&p&&(&/span&&span class=&n&&sock&/span&&span class=&p&&,&/span& &span class=&n&&SOL_TCP&/span&&span class=&p&&,&/span& &span class=&n&&TCP_KEEPIDLE&/span&&span class=&p&&,&/span& &span class=&p&&(&/span&&span class=&kt&&char&/span&&span class=&o&&*&/span&&span class=&p&&)&/span&&span class=&o&&&&/span&&span class=&n&&value&/span&&span class=&p&&,&/span& &span class=&k&&sizeof&/span&&span class=&p&&(&/span&&span class=&kt&&long&/span&&span class=&p&&));&/span&
&span class=&n&&value&/span& &span class=&o&&=&/span& &span class=&n&&keepintvl&/span&&span class=&p&&;&/span&
&span class=&n&&isetsockopt&/span&&span class=&p&&(&/span&&span class=&n&&sock&/span&&span class=&p&&,&/span& &span class=&n&&SOL_TCP&/span&&span class=&p&&,&/span& &span class=&n&&TCP_KEEPINTVL&/span&&span class=&p&&,&/span& &span class=&p&&(&/span&&span class=&kt&&char&/span&&span class=&o&&*&/span&&span class=&p&&)&/span&&span class=&o&&&&/span&&span class=&n&&value&/span&&span class=&p&&,&/span& &span class=&k&&sizeof&/span&&span class=&p&&(&/span&&span class=&kt&&long&/span&&span class=&p&&));&/span&
&span class=&cp&&#elif defined(SO_KEEPALIVE)&/span&
&span class=&n&&value&/span& &span class=&o&&=&/span& &span class=&n&&enable&/span&&span class=&o&&?&/span& &span class=&mi&&1&/span& &span class=&o&&:&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&n&&isetsockopt&/span&&span class=&p&&(&/span&&span class=&n&&sock&/span&&span class=&p&&,&/span& &span class=&n&&SOL_SOCKET&/span&&span class=&p&&,&/span& &span class=&n&&SO_KEEPALIVE&/span&&span class=&p&&,&/span& &span class=&p&&(&/span&&span class=&kt&&char&/span&&span class=&o&&*&/span&&span class=&p&&)&/span&&span class=&o&&&&/span&&span class=&n&&value&/span&&span class=&p&&,&/span& &span class=&k&&sizeof&/span&&span class=&p&&(&/span&&span class=&kt&&long&/span&&span class=&p&&));&/span&
&span class=&cp&&#else&/span&
&span class=&k&&return&/span& &span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&p&&;&/span&
&span class=&cp&&#endif&/span&
&span class=&k&&return&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&br&如今,大部分平台下,确实可以不用自己做心跳了,直接调整下KeepAlive的参数到你期望的水平即可。然而如你所见,代码中大量的条件判断。KeepAlive参数调整的实现对平台依赖很大,并非所有平台都支持这样的,你如果能够确定自己代码运行的平台,那么放心大胆的用这个keepalive。你如果无法确定平台,不想依赖平台的keepalive,又需要自己精确控制链接超时,那么请自己实现心跳。
你ssh连上服务器的情况下,拔掉网线,你任然检测不到网络断开了(没有FIN),这时候把网线插回去,敲两下键盘,终端一样有反应,链接还活着。因为tcp的链接是否有效,依赖链接两端的状态确定,你在你机器上拔掉网线,你是知道这件事情的,但是中间网络设备…
&p&&b&首先,这是个很大的命题,之前在360负责过几个对外的服务的研发,也算是有点小经验,我试着答一下:&/b&&/p&&p&在Internet环境下,安全问题我主要分为如下几类:&/p&&ol&&li&信息传输过程中被黑客窃取&/li&&li&服务器自身的安全&/li&&li&服务端数据的安全&/li&&/ol&&p&首先,如果能用https,就尽量用https,能用nginx等常见服务器,就用常见服务器,主要能避免以下问题:&/p&&ul&&li&自己实现的协议&Server端可能会有各种Bug,被缓冲区溢出攻击等&/li&&li&SSL加密体系在防监听方面已经足够成熟,值得信赖&/li&&/ul&&p&所幸,私有协议服务的攻击需要黑客分析协议,这就给一般的小服务增加了一层保护。但如果是在大公司做事,树大招风,就必须至少做到理论上没有安全漏洞。神马,xor混淆一下,C/S端写死一个对称密钥这种掩耳盗铃的事情就不要做了,不然会死的很难看。&/p&&p&如果需要自己实现Server端,实现一套合格的SSL还是很考验功底的:&/p&&ul&&li&首先要弄明白SSL加密体系密钥交换的原理&/li&&li&对各种对称、非对称加密算法要有深刻的理解&/li&&li&用非对称加密算法怎么实现一套密钥交换体系&/li&&li&如何处理ca证书,在自签名情况下怎么避免中间人攻击&/li&&/ul&&p&工程实现过程中,要考虑:&/p&&ul&&li&各种可能的缓冲区溢出攻击&/li&&li&SYN flood攻击,慢连接攻击&/li&&li&DDoS防起来有难度,但至少能防御DoS攻击&/li&&/ul&&p&业务逻辑层面,要考虑:&/p&&ul&&li&每个接口都要做好用户&权限验证&/li&&li&接口会不会被人乱用,重放攻击&/li&&li&攻击方会不会找到一个比较消耗服务端资源的接口,用很小的代价耗尽服务端资源&/li&&li&用户的用户名密码会不会被通过接口破解,参见:&a href=&///?target=http%3A//en.wikipedia.org/wiki/2014_celebrity_photo_hack& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&2014 celebrity photo hack&i class=&icon-external&&&/i&&/a&&/li&&li&你的服务会不会被黑客利用去攻击别的服务,特别是会根据用户输入抓取什么资源的服务&/li&&li&古老的SQL注入&/li&&li&无耻的仿冒服务,DNS欺诈&/li&&li&涉及HTML的,还要考虑跨站……&/li&&/ul&&p&即使你做到了天衣无缝,还要考虑队友有时会掉链子:&/p&&ul&&li&glibc、openssl这些基础的库也会爆出漏洞,参见:&a href=&///?target=http%3A//en.wikipedia.org/wiki/Heartbleed& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Heartbleed&i class=&icon-external&&&/i&&/a&&/li&&li&同一台主机上的其它服务被攻陷&/li&&/ul&&p&没有踩过很多坑,哪里知道很多事。写完之后整个人都不好了&/p&
首先,这是个很大的命题,之前在360负责过几个对外的服务的研发,也算是有点小经验,我试着答一下:在Internet环境下,安全问题我主要分为如下几类:信息传输过程中被黑客窃取服务器自身的安全服务端数据的安全首先,如果能用https,就尽量用https,能用ngi…
如果什么服务端经验都没有的话,建议上 HTTP,用 PHP 来写&br&HTTP是请求-应答试的,也就是你:登录,提交战斗结果,请求排行榜 之类的动作都是提交 HTTP 请求到 PHP程序。&br&&br&然后客户端20秒轮询一次服务端,问服务端有没有什么消息给我,比如双人聊天消息。&br&如果取到消息,就把下一次轮训时间改短,比如5秒,再取到消息,继续改短,比如2秒,&br&如果没消息就慢慢放长周期,比如:2秒,3秒,5秒,7秒,10秒,15秒,20秒&br&直到有消息了,又再次把周期变短。&br&&br&服务端和客户端之间数据通信用 json,请求就是一个 json 对象过去,php处理,结束后返回一个json回来,客户端收到后跳转到具体处理代码。&br&&br&调试的话服务端客户端可以分开调试,HTTP调试比较简单,直接浏览器或者 wget/fetch 都可以调试。iOS下面我记得有提供 HTTP 访问的各种现成框架,直接使用就得了。&br&&br&选PHP是因为学习周期最短,部署最简单,windows下的话,直接使用:&br&&a href=&///?target=http%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&WampServer, la plate-forme de développement Web sous Windows&i class=&icon-external&&&/i&&/a&&br&&br&来部署,Wamp 一安装,直接自带:apache+php+mysql,
如果什么服务端经验都没有的话,建议上 HTTP,用 PHP 来写 HTTP是请求-应答试的,也就是你:登录,提交战斗结果,请求排行榜 之类的动作都是提交 HTTP 请求到 PHP程序。 然后客户端20秒轮询一次服务端,问服务端有没有什么消息给我,比如双人聊天消息。 如…
实名赞同 &a data-hash=&670a7d8b6f1ebed21f1ec00& href=&///people/670a7d8b6f1ebed21f1ec00& class=&member_mention& data-editable=&true& data-title=&@DalYz& data-hovercard=&p$b$670a7d8b6f1ebed21f1ec00&&@DalYz&/a&的回答&br&补充一个资料:&br&&a href=&///?target=http%3A//blog.csdn.net/gddsky/article/details/1831658& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&深入剖析MMORPG游戏服务器端的设计&i class=&icon-external&&&/i&&/a&&br& MMORPG不同于其它的局域网的网络游戏,它是一个面向整个Internet的连接人数过万的网络游戏,因此他的服务器端设计则极为重要&br&&br&&strong&服务器的基本设置&/strong&&br&&br&  在大型网络游戏里,通常设计为C/S结构,客户端不再对数据进行逻辑处理,而只是一个收发装置,从玩家那里接受到操作信息,然后反馈给服务器,再由服务器进行处理后发回客户端,经客户端通过图形化处理,给玩家呈现出一个缤纷的游戏世界。&br&&img src=&/9d09ebef63d185cbdaf1_b.png& data-rawwidth=&375& data-rawheight=&220& class=&content_image& width=&375&&&br&&br&  登陆服务器&br&&br&  在这里也可以称之为连接服务器,网络游戏的客户端一般是连接到这里,然后再由该连接服务器根据不同的需要,把游戏消息转发给其它相应的服务器(逻辑和地图服务器)也因为它是客户端直接连接的对象,它同时也负担了验证客户身份的工作。&br&&br&  地图服务器&br&&br&  在这里也可以称之为连续事件服务器。在这个服务器里要处理的对象(玩家)所做的动作都是一个连续事件。例如玩家从A点移动到B点,这样一个动作,需要一定的时间进行移动,因此说移动是一个连续事件。&br&&br&  逻辑服务器&br&&br&  在这里可以称之为瞬时事件服务器,在这个服务器里,处理对象(玩家)所做的动作均可以在非常断时间内完成完成。例如玩家从商店购买一瓶药书,当玩家确认购买后,服务器先扣除玩家的游戏币,然后再把相应的药水瓶加入玩家的背包里。这2个操作对于服务器来说,只是2个数字的加减,计算完这两个数字的加减,这个事件就可以结束了。因此,我们可以说这个事件是一个瞬时事件&br&&br&&strong&服务器组的改进&/strong&&br&&br&  不过在实际应用的过程中,游戏服务器的结构要比

我要回帖

更多关于 如何看待游戏里处cp 的文章

 

随机推荐