2

封装socket

 3 years ago
source link: https://www.zenlife.tk/wrap-socket.md
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

封装socket

2016-05-03

五一宅在家里写代码,估计删删改改的有个1000多行吧。云风常常一个周末敲个大几千行的东西出来,我还是比较相信的。对于撸自己比较熟习的东西,一遍一遍,每次都期望比上次做的更好一些。基本上一个东西,要写上好多次,代码和设计才会变得优雅。

自己也是那种不写代码手会痒的类型,每隔一段时间,狂暴的血液就会沸腾一阵。那种写到半夜2、3点,越写越兴奋停不下来的感觉。大概真正的程序员都经历过这感觉。这次是封装socket玩。

估计除了新手第一次看socket,没有人平时写代码是socket/bind/listen不做封装拿着用的。更多的是使用现成和库或框架。其实封装socket是个挺好玩的事情。

我希望封装后应该是方便使用的API,喜欢的类型是那种,来一个请求,开一个thread那种同步的API。我希望它是上层阻塞底层不阻塞的,希望thread是轻量的。最后,它的代码看起来大概应该是这个样子

我说的封装,就是一步一步的弄到这个demo能跑起来(废话)。过程是一层一层的。

第一步是封装C给lua用。封装基本的socket,以及epoll。我封装得比较浅,基本就没做太多,只是把C导出到lua了。关于epoll封装,云风写过一遍回调还是消息队列,我是蛮认同的。C让回调lua是一个不太干净的东西,lua应该是在C之上更高层次。

这一层还涉及到一个跨平台的事情,尽量把平台相关的在比较低层次的API中封装了,高层就可以统一。开始自己写完epoll的,发现kqueue还是不太熟,又要偷懒,后来干脆直接“偷”了云风封装好的头文件来用。

封装到这里的时候可以在lua里面交互式的跑一跑,这种交互式的跑tcp的感觉还是挺好玩的,中间可以把各步骤断点下来,看tcp状态变迁之类的。

第二步是封装异步API。还是按中规中矩的写法来的,异步API代码大概是这样子的。其实封装成这样子也是能够用了,就是我不喜欢写异步,不喜欢事件循环,每次看到异步回调就感觉很反人类。

但是这一步是需要做的,之前写过博客就说过,异步是同步的基础,lua提供了协程机制,在异步之上封装同步不难。

写异步API封装这一块代码的时候,要多注意下细节处理,这是比较考验基本功的。epoll的水平边沿触发;各种error区分;读到长度为0关连接;主动关连接时刷缓冲保证数据可靠传输。呵呵,我这是demo代码乱写的,别当真。

write是异步写的,也就是先保存在应用缓冲,到EPOLLOUT事件的时候刷过去。还有记得刷完后关掉EPOLLOUT,不然会一直提醒的。说到这里,lua如何用两个table来模拟队列,而且还要支持推回队列头功能,可以去看代码。推回队头需求是因为write可能到缓冲写满,这里我们要把剩下的数据先推回去,下次再发。

第三步了,封装成同步API。

有lua的协程支持,异步封装同步很容易的。就这几行:

local Conn = AsyncConn:new() –继承异步的类 function Conn:read() self.co = coroutine.running() –保存当前coroutine供将来使用 return coroutine.yield() –yield掉会阻塞的操作 end function Conn:onread(data) –重载onread回调 coroutine.resume(self.co) –在回调函数中恢复之前保存的coroutine end

Thread还是要的。直接协程不太方便,可以封装一下。 lua的协程是非对称协程,就是不能够A B之间相互的resume,只好A yield加调度器,由调度器决定resume到B,B也是先yield回调度器而不是直接到A。

还有遇到的一个问题是,我开始把eventloop作为一个普通的Thread跑,但是新建的Thread跟eventloop的Thread谁先调谁后调,时序问题容易出bug。我在eventloop里面是不做yield了的,这样异步API干净一点不会跟Thread有耦合。于是有一种情况是:新建出来的Thread并没有立刻执行而不放到队列尾,执行到eventloop的线程,系统便不再切换了,新线程永远得不到机会运行。 所以后来我把eventloop单独抽到完全没有用户线程可执行了才调用。

还有一个很扯的,connection加到事件循环的时机问题。要等线程建出来之后,否则线程还没出来,有事件来了Conn:on_read就回调,这时里面的self.co还是空指针。

最后。。。这是个概念验证级别的的demo。确信自己能弄出喜欢的socket风格来。

目前这个demo中是用的cotoutine来模拟线程的。 完整的将是grt这个项目(代码所属的父级目录),几年前挖的坑,至今还没填的。其实就是Go的那一套runtime,用C+lua完全可以弄出来。线程和lua虚拟机M:N的调度来充分压榨CPU,channel通信,基于协程的轻量并发,友好的同步API(上层阻塞而底层不阻塞)。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK