欢迎来到皮皮网网首页

【在线博客系统源码】【吉祥网站源码论坛】【贷款自动审核源码】pong游戏源码_app游戏源码

来源:云授权V6源码 时间:2024-11-24 20:35:14

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的特性和应用。

       对于完全零基础的吉祥网站源码论坛小伙伴,我推荐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,就直接使用它。源动力源码

       为什么要这么做呢?这是因为事务篇中提到的修改操作具有“传染性”,修改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中负责维护连接的活性,但双向心跳机制在某些场景下更为全面。