1.二手买了一个8p说只能当游戏机,游戏源码p游不能插卡卖家说修不了什么问题
2.Netty åºç° Connection reset by peer å¼å¸¸çå 个åå
3.自学编程入门,戏源先学什么语言好?
4.gin框架原理详解(gin框架是什么)
5.BoltDB源码解析(七)Put和Delete操作
6.Netty IdleStateHandler心跳机制
二手买了一个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的特性和应用。
对于完全零基础的源码下载818源码小伙伴,我推荐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中负责维护连接的活性,但双向心跳机制在某些场景下更为全面。