皮皮网
皮皮网

【房屋收租管理软件 源码】【java 源码讲解】【游戏改名源码】pong游戏源码_app游戏源码

时间:2024-12-27 14:20:43 来源:dogedoge源码

1.二手买了一个8p说只能当游戏机,游戏源码p游不能插卡卖家说修不了什么问题
2.Netty 出现 Connection reset by peer 异常的几个原因
3.自学编程入门,戏源先学什么语言好?
4.gin框架原理详解(gin框架是什么)
5.BoltDB源码解析(七)Put和Delete操作
6.Netty IdleStateHandler心跳机制

pong游戏源码_app游戏源码

二手买了一个8p说只能当游戏机,不能插卡卖家说修不了什么问题

       是游戏源码p游翻新机,所以修不了。戏源

       游戏机是游戏源码p游一种主要用于娱乐,使用只向获得许可的戏源房屋收租管理软件 源码软件开发者开放的源代码,以电视机或其他专用显示器以及专用输入设备的游戏源码p游电脑系统。其与个人计算机最大的戏源区别在于源代码和软件的封闭性。

       每当提到大型游戏机,游戏源码p游人们最容易想到的戏源就是在游戏厅当中沉迷的孩子们,而这样的游戏源码p游场景,在上个世纪年代曾出现在国内大小城市里。戏源紧接着到网吧出现以后,游戏源码p游大型游戏机才逐渐的戏源冷淡下来,但其却在艰难的游戏源码p游环境下发展,殊不知已成了当今过百亿美元的出口货值。

       年,雅达利(Atari)公司发售了一种平台式大型游戏机“乒乓”(PONG),该游戏机风靡全美。同年,世界上第一台用“电视”玩的电子游戏诞生了,MagnavoxOdyssey是世界上第一台电视游戏机。

Netty 出现 Connection reset by peer 异常的几个原因

       æœ€è¿‘使用 netty 过程中发现了几个比较细节的 Connection reset by peer 异常,做个笔记。

        这个场景出现在用 Jedis ping 检测的场景,用完直接 close,服务端稳定出现 Connection reset by peer。

        tcpdump 一下就很容易定位到问题所在,客户端收到 PONG 响应后直接发了一个 RST 包给服务端:

        查看 Jedis 的源码发现 socket 有个比较特殊的配置 socket.setSoLinger(true, 0) 。

        先看一下 man7/socket.7 的解释:

        坦白说不是很明白啥意思。。。

        最终在 stackoverflow 上找到一个比较容易理解的解释:

        简而言之,设置 SO_LINGER(0) 可以不进行四次挥手直接关闭 TCP 连接,在协议交互上就是直接发 RST 包,这样的好处是可以避免长时间处于 TIME_WAIT 状态,当然 TIME_WAIT 存在也是有原因的,大部分评论都不建议这样配置。

        这个场景有点儿微妙,首先得理解一下 tcp 的两个队列。

        这篇文章讲得比较清楚: SYN packet handling in the wild

        accept 队列满通常是由于 netty boss 线程处理慢,特别是在容器化之后,服务刚启动的时候很容易出现 CPU 受限。

        为了模拟这个现象,我写了个示例程序 shichaoyuan/netty-backlog-test ,设置 SO_BACKLOG 为 1,并且在 accept 第一个连接后设置 autoRead 为 false,也就是让 boss 线程不再继续 accept 连接。

        启动第一个 Client,可以正常连接,发送 PING,接收 PONG。

        启动第二个 Client,也可以正常连接,但是没有收到 PONG:

        可见这个连接创建成功了,已经在 Accept Queue 里了,但是进程没有 accept,所以没有与进程绑定。

        启动第三个 Client,也可以正常连接,也没有收到 PONG:

        与第二个连接一样。

        启动第四个 Client,也可以正常连接,但是在发送 PING 后出现 Connection reset by peer:

        这个连接在服务端并没有进入 accept queue,处于 SYN_RECV 状态,并且很快就消失了(因为 accept queue 已经满了,无法转入 ESTABLISHED 状态)。

        抓包看一下:

        从客户端视角来看连接确实是建成功了,有一个比较特殊的地方在三次握手之后,服务端又向客户端发送了一个 [S.],客户端回复了一个 [.],这个交互看起来不影响连接。

        服务端后来销毁了连接,而客户端还认为连接是 ESTABLISHED 的,发送 PING 消息,服务端自然得回复一个 RST。

        PS:我在 Windows 的 WSL2 中实验这种场景是建连接超时,可能不同的操作系统或 linux 版本对这个交互的过程处理不同,在此不进行进一步测试了。

        以上,这个故事告诉我们判断连接是否可用,建成功之后应该发个心跳包测试一下。

自学编程入门,先学什么语言好?

       入门编程,选择Python作为第一门语言是一个明智的选择。Python语言简洁易懂,功能强大,适合新手快速上手。下面,我将推荐几个适合Python新手学习和实践的开源项目,帮助你更好地掌握这门语言。

       首先,对于有编程基础的小伙伴,我推荐《Python之旅》开源书。这本书虽然定位入门级,但并不适合手把手教你安装环境等基础操作,而是提供更深入的Python知识。如果你已经掌握其他编程语言,具备一定的编程基础,那么《Python之旅》将会是一个很好的选择,帮助你深入理解Python的特性和应用。

       对于完全零基础的java 源码讲解小伙伴,我推荐Python--Days项目。这个项目非常全面,从Python基本语法开始,到进阶知识、Linux基础、数据库、Web开发、爬虫、数据分析和机器学习等,几乎涵盖了Python学习的所有方面。它采用循序渐进、手把手教学的方式,非常适合新手从零开始学习Python。

       此外,如果你对Python有更深入的学习需求,可以参考Python最佳实践指南和Python Cookbook。这些资源将帮助你提升编程能力,学习如何更优雅地使用Python,解决实际问题。同时,它们也提供了丰富的案例和技巧,帮助你提高代码质量和效率。

       对于喜欢通过游戏学习编程的小伙伴,我推荐free-python-games项目。这个项目包含了一些简单的小游戏,如贪吃蛇、迷宫、Pong等,通过游戏可以轻松学习Python编程。此外,KeymouseGo和/s/1SX3Gjq... 密码:2eev)。在实际操作中,不仅要能够将项目运行起来,更重要的是去阅读源码、理解和修改代码,这样才能真正掌握Python编程。

       加入HelloGitHub交流群,与其他编程爱好者交流,获取更多学习资源和项目实践机会,同时也可以参与开源项目贡献。游戏改名源码无论是C、C++、Java、Go、Python、前端、机器学习等技术领域,还是大学生开源群,HelloGitHub都为你提供了与业界大佬交流的平台。关注HelloGitHub,添加为好友,入群一起探索编程的乐趣和挑战!

gin框架原理详解(gin框架是什么)

       Gin的启动过程、路由及上下文源码解读

       Engine是gin框架的一个实例,它包含了多路复用器、中间件和配置中心。

       gin通过Engine.Run(addr...string)来启动服务,最终调用的是/手败gin-gonic/gin

       一个简单的例子:

       packagemain

       import"github.com/gin-gonic/gin"

       funcmain(){

       //Default返回一个默认的路由引擎

       r:=gin.Default()

       r.GET("/ping",func(c*gin.Context){

       //输出json结果给调用方

       c.JSON(,gin.H{

       "message":"pong",

       })

       })

       r.Run()//listenandserveon0.0.0.0:

       }

       编译运行程序,打开浏览器,访问页面显示:

       { "message":"pong"}

       gin的功能不只是简单输出Json数据。它是一个轻量级的WEB框架,支持RestFull风格API,支持GET,POST,PUT,PATCH,DELETE,OPTIONS等/gin-gonic/gin"

       )

       funcmain(){

       router:=gin.Default()

       //静态资源加载,本例为css,js以及资源

       router.StaticFS("/public",/ffhelicopter/tmm/website/static"))

       router.StaticFile("/favicon.ico","./resources/favicon.ico")

       //Listenandserveon0.0.0.0:

       router.Run(":")

       }

       首先需要是生成一个Engine,这是gin的核心,默认带有Logger和Recovery两个中间件。

       router:=gin.Default()

       StaticFile是加载单个文件,而StaticFS是加载一个完整的目录资源:

       func(group*RouterGroup)StaticFile(relativePath,filepathstring)IRoutes

       func(group*RouterGroup)StaticFS(relativePathstring,fs/gin-gonic/gin

       如果安装失败,直接去Githubclone下来,放置到对应的目录即可。

       (2)代码中使用:

       下面是一个使用Gin的简单例子:

       packagemain

       import(

       "github.com/gin-gonic/gin"

       )

       funcmain(){

       router:=gin.Default()

       router.GET("/ping",func(c*gin.Context){

       c.JSON(,gin.H{

       "message":"pong",

       })

       })

       router.Run(":")//listenandserveon0.0.0.0:

       }

       简单几行代码,就能实现一个web服务。使用gin的Default方法创建一个路由handler。然后通过HTTP方法绑定路由规则和路由函数。不同于net/e"}。华为招聘源码

       注:Gin还包含更多的返回方法如c.String,c.HTML,c.XML等,请自行了解。可以方便的返回HTML数据

       我们在之前的组v1路由下新定义一个路由:

       下面我们访问

       可以看到,通过c.Param(“key”)方法,Gin成功捕获了url请求路径中的参数。同理,gin也可以捕获常规参数,如下代码所示:

       在浏览器输入以下代码:

       通过c.Query(“key”)可以成功接收到url参数,c.DefaultQuery在参数不存在的情况下,会由其默认值代替。

       我们还可以为Gin定义一些默认路由:

       这时候,我们访问一个不存在的页面:

       返回如下所示:

       下面我们测试在Gin里面使用Post

       在测试端输入:

       附带发送的数据,测试即可。记住需要使用POST方法.

       继续修改,将PostHandler的函数修改如下

       测试工具输入:

       发送的内容输入:

       返回结果如下:

       备注:此处需要指定Content-Type为application/x-www-form-urlencoded,否则识别不出来。

       一定要选择对应的PUT或者DELETE方法。

       Gin框架快速的创建路由

       能够方便的创建分组

       支持url正则表达式

       支持参数查找(c.Paramc.Queryc.PostForm)

       请求方法精准匹配

       支持处理

       快速的返回给客户端数据,常用的c.Stringc.JSONc.Data

BoltDB源码解析(七)Put和Delete操作

       Put和Delete的实现

       上一篇文章我们了解了BoltDB的Get API的实现。现在,我们来探讨Put和Delete API的实现:

       Put API的主要功能是将一对键值对插入到Bucket中,如果键已经存在,则更新对应的值。首先,进行一些限制条件的检查,例如Put操作是否由写事务发起的,因为Put只能由写事务调用。此外,还需要检查键和值的大小是否符合限制条件。需要注意的是,Put操作和Get操作一样,这里也使用了Cursor来定位键应该放置的位置。

       在实际的Put操作中,会调用Cursor的一个不显眼的方法:

       这个方法实际上非常有用,它从当前Bucket的B-tree的根节点开始,一直到Cursor定位到的leaf page,为每个page创建一个对应的node结构。当然,如果一个page已经有对应的node,就直接使用它。dsp源码下载

       为什么要这么做呢?这是因为事务篇中提到的修改操作具有“传染性”,修改B-tree的leaf节点会导致从root到leaf的所有page都需要修改,而BoltDB的修改操作都是在page对应的node里进行的,不是直接在page上修改,因此需要为这些page建立node结构。具体建立node结构的是Bucket的node方法:

       Bucket的node方法有两处需要注意,一个是新建的node会被追加到parent node的children中,记录下这些修改的node之间的关系,这个children在node持久化时会有用(node.spill方法)。另一个是node的数据是如何从page中读取的,这是由node的read方法完成的。

       node建立好之后,就在要修改的leaf对应的node上调用put方法:

       node的put方法相对简单,它是在inodes数组上查找对应的位置,如果exact为true,表示找到了相同的key,直接更新value;如果exact为false,相当于找到了应该插入的位置,然后在对应的inode上记录数据。我们来看一下inodes数组的定义:

       inodes数组是node实际存储数据的地方,由多个inode组成,每个不同的key对应一个不同的inode,inode之间是按key排序的。对于leaf节点来说,inode里使用key和value;对于branch节点来说,inode里使用key和pgid,pgid代表一个child page的id。value和pgid不会同时使用。

       put方法结束后,当前的Put操作也就结束了。也就是说,Put操作所做的仅仅是把新增或修改的数据放入到它所在的page对应的node内存中。

       顺便提一下Delete操作,它和Put操作非常类似,在建立起node结构之后,在对应的node的inodes数组中删除找到的key相等的inode就完成了,这里不再展开。

       那么,什么时候会把这些node里的数据持久化到DB文件里呢?是在整个写事务commit的时候。

       事务的Commit实现

       下面是事务commit的代码简化,保留了重要部分:

       Commit的整体流程比较长,下面一点一点进行说明。

       tx.root.rebalance(),这个root是root Bucket,rebalance是对root Bucket下所有子Bucket的所有node进行rebalance。这是什么意思?注意node的初始数据虽然来自一个page,但在经历了一些Delete操作后,有些node里面的数据可能过少,这时会先把这个node和它的左兄弟或右兄弟node合并(node的rebalance方法),合并后node数会减少,但不存在node里数据过少的情况。这个操作对应于B-tree的merge操作,只不过这些node都是Go的内存结构,合并起来非常简单。当然,合并后把这些node spill到page的操作,需要的page总数也会减少。

       tx.root.spill(),这个方法是把root Bucket下所有子Bucket的所有node的内容都写入这个事务分配的dirty page里。注意这些dirty page是这个事务临时分配在内存里的,结构和DB文件的page完全一样,但还不是mmap映射的DB的page。

       刚开始看到spill这个方法时,感觉它代价有些高,感觉像是把整个B-tree都走了一遍。后来仔细看才发现不是这么回事。这个spill只对有node结构的节点进行处理,那些没修改过的page没有对应的node,根本不会处理。

       注意在经过多次Put操作后,node里存放的数据可能出现一个page写不下的情况,比如insert了几千个key value。spill会先把这样的node split成多个大小合适的node(node的split方法),然后把这些node分别写入不同的page中。这个操作对应于B-tree的split操作。和rebalance方法类似的道理,因为这些node都是Go的内存结构,split起来非常容易。

       if tx.meta.pgid > opgid,这个判断是看当前事务需要的page数是否大于事务执行前DB文件有的page数,如果大于,说明DB文件放不下了,就调用db.grow增大文件,以容纳新增的page。

       紧接着是freelist的持久化操作,因为写事务可能使用了freelist里的一些page,同时也可能释放了一些page到freelist里,所以freelist很可能发生了变化,需要持久化。

       tx.write(),这个方法就是把所有的临时分配的dirty page都写入DB文件对应的page里。

       tx.writeMeta(),这个方法是把这个tx里的meta写到meta0或者meta1里面(写事务会交替写这两个meta page,这也是个常用技术,叫ping-pong buffer)。它的代码值得看一下:

       首先把meta写到临时分配的buf里,然后用文件IO写到DB文件里,最后调用fdatasync,把OS文件的buffer cache持久化到磁盘上。至此,写事务的所有数据都已经落盘完毕。后面新开启的事务会因为这个meta的txid是最大的,而选择使用这个最新的meta page。而这个meta page包括最新的root bucket,最新的freelist,最新的pgid,这些总体构成了一个DB的最新版本,保证新开启的事务读到最新版本的数据。

       看tx.write()和tx.writeMeta()的实现可以发现,写入数据用的是db.ops.writeAt,而这个方法默认值就是File.WriteAt方法,所以实际写入文件用的是文件IO,而不是直接写mmap内存。而BoltDB使用mmap一开始就把mmap映射的内存标记为只读的,压根不允许直接写mmap内存。为什么要这么做呢?

       猜测可能是为了安全。前面讲到Get操作为了性能是zero copy的,发现Get返回来的value是mmap上数据的指针,如果mmap设置为可读写的,应用程序代码五花八门,可能会通过指针一不小心修改了mmap上的数据,这样的修改因为走的不是API是无法保证事务的。把mmap设置为只读的消除了这种可能性。反过来说,如果mmap设置为可读写的,Get就不能返回mmap上的指针了,为了安全一定要copy一份数据出来才行,降低了Get的性能。

       这里还有个很自然而且很重要的问题是,如果事务commit失败了呢,BoltDB如何保证事务的原子性(ACID的A),确保这个写事务的所有操作,不论是落盘的,还是没落盘的,都不会生效?

       原子性要求,不管是commit走到哪一步,哪怕是已经把修改的数据,甚至包括修改的freelist已经落盘,只要最终事务commit失败,都不能对正确性产生任何影响。这里的正确性是指,数据库的状态(有实际的key value数据,freelist, pgid等共同构成)必须是在这个写事务运行之前的状态,数据不能被破坏,这个写事务也不能留下可被后续事务读到的任何更新。

       要做到原子性貌似挺难的,因为事务的commit里包括很多步骤,这些步骤都不是原子性的。不过重要的一点是,不论commit运行到哪一步,因为tx.writeMeta是最后一步,只有这一步运行成功commit才算成功,如果说commit失败了,那么tx.writeMeta一定是没运行,或者运行了半截,这个meta page没写完整,机器断电了。总之,这些情况下我们不会得到一个合法的新的meta page(这种情况下meta的validate方法会失败,因为meta的checksum不对)。这时候ping-pong buffer的meta page就起重要的作用了,因为交替写meta page的原因,即使这个写事务新的meta page没写成功,这个写事务运行前版本的meta page还在,而这个meta page包括这个写事务运行前的DB版本所有的状态(kv数据,freelist,pgid等)。这个meta page会被后续事务使用,就像那个失败的写事务从来没有运行过一样。而那个写事务留下的kv数据的page,freelist的page,即使是持久化了,也因为没有写成新的meta,没有机会被用到。

       还有个自然的疑问,即使这个失败的写事务写的page因为没有合法的meta无法被引用,不会影响正确性,但无法被引用是不是也意味着这些page无法被回收,浪费了磁盘空间?

       答案是也不会。在原来版本的meta里的free list和pgid的共同作用下,这些page会被视为free的,还可以使用,不会出现无法回收这些page的情况。

       还有个疑问,既然BoltDB交替写meta0和meta1,是不是连续两个事务commit正好在写meta时失败,数据库就废了?

       仔细研究发现,还是没事!因为写事务的txid也是meta的一部分,一个写事务失败,导致txid不会增长,下一次写事务的txid还是一样,meta的交替写是因为txid的变化引起的,既然没变化,就不交替了。所以下一个写事务即使写meta还失败了,也还是写的上一个写事务写的那个meta,不会把两个meta都写坏。

       总结一下,ping-pong buffer的meta page真是设计得精巧,是BoltDB达到原子性的关键!

Netty IdleStateHandler心跳机制

       Netty的IdleStateHandler心跳机制在TCP长连接中扮演重要角色,确保连接的有效性。它并非严格的PING-PONG交互,而是客户端主动发送心跳,服务器接收但不回复,以节省网络资源。当双方长时间无数据交互,即进入idle状态时,IdleStateHandler会定时检测,如超时则触发userEventTriggered()方法。

       Netty提供了IdleStateHandler来处理空闲连接和超时问题,它在服务器端添加处理器,每五秒检查一次读操作,五秒内无数据读取则触发事件。客户端则每四秒发送心跳,通过write()方法检测,四秒内无写操作同样触发事件。IdleStateHandler构造器中,可以自定义readerIdleTime和writerIdleTime,设置读写空闲超时时间。

       源码中,IdleStateHandler通过定时任务监控channelRead()和write()方法的调用,一旦超时,会执行userEventTrigger()。服务端仅响应客户端的心跳,避免大规模响应带来的资源浪费。然而,这样设计意味着客户端无法感知服务端的非正常下线,如网络故障。

       为了实现双向心跳感知,可能需要在ChannelInactive()方法中进行补充,以应对非正常下线情况。总的来说,IdleStateHandler在Netty中负责维护连接的活性,但双向心跳机制在某些场景下更为全面。

更多内容请点击【综合】专栏