Delphi IOCP完成端口服务端开发的一些记录

     这段时间,腾讯课堂各种教程各种作,看看真是热闹啊,就是没有Delphi的课程。

     啊,你说有,屁的有,大牛一个都不见,真是冷清啊。

     C++那个啥,叫逼得多厉害,高并发服务端大型项目等你来啊。

     啥,你不打算参加大型项目的开发??不打算赚大钱?

     其实真是的,我在想啊,真有大型项目,轮得到咱们去做?去,真有那么多大型项目,他们这些人用得着卖力地赚麦吗?

     都是大神级别的,其实我从自己出发,于是我永远相信一条,真正的大神赚的永远是大钱,哪有这个时间给你上课?

     说下,你哪来的时间上课???你需要混这些钱????

     退休了,就说退休了。不是看不起你,你真的比我牛。但是你真的退休了,咱呢,还需要继续奋斗。

     不是看不起你,其实真的....唉。看看真是悲剧,一个程序员变成了营销人员,各种吹,各种作的。

     不是没有技术,真有,写代码多么滑溜,熟啊,混了那么多年.......嗯,现在创业不容易。学了啥跟啥,保证大型项目等你来赚,嘻嘻,写代码写到脑子抽筋的就是这种了。骗骗学生就好,不过那样确实让人看了,感觉那个伤感啊。为这些程序员不值,不知道被那个公司踢了出去。混口饭不容易,懂了。

     说真的,要开发一套稳定的IOCP完成端口并不容易,不管你采用哪种语言去开发,虽然网上有不少资料。但是如果你不具有服务端开发的一些经验,那么你会发现到得最后,开发出来的玩意,永远达不到你的要求。

    比如大量那些所谓粘包之类的论调等等,会严重误导你的代码结构,本来想开发高效的服务器控件,最终开发出来的玩意,确实只能算是玩意而已。

    难啊,难的是你很难得到正确的信息,人云亦云的那些东西大多数是很多没有游戏服务端维护和开发的人一些学习加测试所谓的经验。

    那些很悲剧的经验,因为你不知道这些经验是否是真的正确,在此之前,你会认为是正确的,若干年后,你才会发觉当初是多么麻痹的了。看,需要时间才能证明结局,这难道不是一件无比悲剧的事情??

    于是乎,为了解决这些你所遇到的所谓问题,你就会不断地纠结——纠结纠结,代码一堆堆地堆上去,各种解决方案,各种折腾........比如粘包就是个最大的纠结。其实就是个笑话!那么难道没有粘包吗,有啊,有就好啊,谁说不好啊?

    呃,悲剧的半包,完成端口真有那么低效??于是又是各种折腾折腾.........

    系统内核没有完成发送任务,自己返回了,呃,客户端断开了吧,或者网络异常了吧,呃,你还要检测是否数据没有发送完成,于是继续折腾????难为的........【我不知道是否有这种数据没有发送完毕,完成线程自己就返回的情况,指的是服务器是在正常的状态下,毕竟没有遇到,而且有些资料明确提到,这是个天大的笑话,我也姑且这样看待,于是控件里面保留了检测代码,十分希望在某年某日某时看到这个检测的返回。】

    为什么不考虑客户端的情况???你确定客户端没有异常情况发生。

    嘿,太多了,多到你头疼。所以不能啥都赖上完成端口。

    另外粘包为什么是个笑话,得说清楚,不能标题党!

    TCP网络模型是避免不了粘包的存在,关于这方面,资料太多了。不再重复。咱说重点:

    一句话说绝,任何人把网络数据都视为绝对安全的人,脑子都是入水了。

    服务端发送数据过来,你不需要校验一下?然后你不打算按照协议来解包?你确定数据包没有附带任何乱码?

    看,客户端这方需要做很多工作,才能保证数据的正确性。现在你还觉得粘包是个大问题?其实你完全按照自己定义的协议解包,这个是最正确的做法,如果超出协议的数据尾,又检测不到协议头的数据,那么这里奉劝你扔掉,否则你就等着客户端崩溃。为什么崩溃,乱码或者其他。有,绝对有!

    现在你还纠结粘包???什么所谓的半包???

    服务器系统资源被占满导致意外返回的情况很难说没有,然而,这些我们做不了太多,除非服务器放在你家里,并且是独占运行。

    于是,所以,直到目前为止,Delphi这块还没有成熟的完成端口解决方案,至于那些所谓多么强悍功能的什么控件,得了吧,我能告诉你的就是,那些都不适合你,你只是需要一套稳定运行的IOCP原生控件就足够了。

   至于那些什么池啊池啊这些绕晕人的东西,你无需花费太多的精力去维护那些玩意,你把每一个链接的客户端处理成一个个的对象,本来就应该这样,服务端管理的目的是什么啊,还不是一个个的客户端,你处理成一个个的对象才是正确的结构。而每一个对象包含有属于自己的那两块的接收缓冲和发送缓冲内存用于数据处理就KO了。

   看看,那些跟平时咱们编程的时候分配的内存指针变量没有啥区别,去,一大堆废话,一大堆代码,没有一个直白一些的。不就是一块内存吗?非要搞得觅死觅活的,何必呢。

   那么内存管理呢,管个屁啊,管理好每一个客户端对象就好,你应该从分散的过程式架构跳到面向对象这块来。对象是不需要指定它非得属于那个客户端的,在客户端链接正常的时候,它是绑定到这个客户端,但是如果这个客户端玩完了,那么就应该重置对象参数,然后扔入空闲链接,等待下一个新的客户端链接,看看,就那么简单,你需要做的工作非常少。

   你的代码非常少,少得真是可啊怜。但是呢,无疑这种简单直接粗暴的结构却保证你的服务端更加稳定和高效!

   工作者线程不要哪种if..else..if...else哪种结构,咱们在IO数据结构附带一个集合就好,这样我们就可以采用Case..of begin..end;这样一种直观高效的方式,按照线程的返回类型,从而获取到我们想要的结果。记住,在工作者线程里面,不要做多余的工作,这些不属于它该管的。否则你就等着各种错误回报你的辛苦工作!!!

    不要做,千万千万不要做,原因是什么,下面一个例子来说明:

    

if Assigned(pIoContext) then
if (BytesTransfered = 0) then begin
    if (pIo.m_OpType = RECV_Mode) or (pIo.m_OpType = SEND_Mode) then begin
       SystemMsgEvent(Format('dwBytesTransfered = 0 客户端 %s:%d 断开连接.',
                               [inet_ntoa(SOCKADDR_IN(pClient.m_ClientAddr).sin_addr),
                               ntohs(SOCKADDR_IN(pClient.m_ClientAddr).sin_port)]));
    end;
end;

看,就那样简单,客户端自己退出,完成线程返回,当时可能是投递发送数据,或者接收数据投递,总之是触发了检测,于是在客户端退出后,服务端直接崩溃掉,系统界面死掉了,点啥都没有反应,死锁现象。

      就是上面这个检测导致的问题,看如此简单的一个检测,居然导致服务端挂掉,严重不?

     为什么呢,系统报告:非法访问某某.......地址是某某,呵呵,常见的报错了。

    pClient是非nil的,出错的是pClientContext.m_ClientAddr  因为线程是非正常退出,所以读取这个结构的时候出现了非法访问。为什么说pClient是非nil的呢,因为下面我调用了客户端断开事件,输入的参数是这个pClient.ClientSocket,通过这个套接字来获取客户端对象进行处理,结果是正常的。

     那么就是说,你最好不要在工作者线程里面做任何无意义的逻辑性的读取检测等动作,因为你无法判断这些指针是否都是正常无误的。我们只要在正常返回的函数里面正确收发数据就好。

     很多时候,问题都是我们自己弄的。

    为了保证链接的正确性,有些人喜欢把心跳包的代码硬生生地插入到完成端口代码里面,为什么说是硬生生的,因为这些不属于完成端口该管的,这些是应用程序自己看情况做的工作。于是我们看到的是各种奇怪的代码架构。写一个心跳包其实哪有那么多的问题,循环线程按照时间发包接包而已,弄那么复杂干嘛?写进完成端口里面干嘛?各种错误。

     下面一种情况,网上搜索到的关于完成端口的问题:

    套接字的唯一性:
            一个正常的Socket值应该是唯一的。但是,操作系统分配的Socket值是随机分配的,

          刚关闭的Socket可能又重新分配给同时新连接进来的套接字。           

           比如:

         1、为新连接分配一个值为3947的Socket

          2、逻辑服务器使用套Socket读取数据包。

          3、在逻辑服务器不知情的情况下,客户端套接字突然关闭。

          4、一个不同的Socket被分配相同的Socket值,即该Socket值的复用。

          5、 一个不同的Socket被分配相同的Socket值,即该Socket值的复用。

         服务器逻辑将数据包写入Socket对应数据,服务器并没有什么问题,但数据包可能会发送到不同的用户。

        为了防止这种棘手的情况...........................................于是各种折腾........................................

        这个问题我感觉很奇怪,外国人怎么会有这个问题?

        我的测试是,客户端断开链接,线程立即返回,不管最初阻塞的是接收投递或者发送投递,线程都会立即返回。这个时候,我们才会重新处理这个套接字上面的事情。那么这个时候整个结构的数据我们都是需要重新重置的。就算立即被分配给新链接的客户端,也只会只能填充这个客户端的信息吧。那么会把数据包发送给不同的用户这个问题永远都不会出现,就算极端的情况下,也只会影响到服务端的信息处理,因为客户端第一组数据是服务端接收的,至于发不发信息给客户端,完全由服务端当时处理意见。这是应用程序的事情,跟完成端口没有半点关系。

        除非,他当时向完成端口投递了很多接收数据的请求?不对的,这个流程本身就有问题。

       看看,折腾吧,我说过很多时候,各种BUG,各种稀奇古怪的问题都是自己作的。

        那么完成端口真正需要处理,应该是各种跟客户端链接和断开链接的资源处理。只要保证不出现非法访问,一般来说,程序是稳定的。但是有一个问题一定需要注意,绝对不要对系统发出的数据进行修改操作,比如监听套接字等等。

        另外退出系统资源,等待系统线程全部退出的操作采用另一条线程来处理,这条线程全程处于阻塞空闲状态,只有服务端停止工作后才调用。

        记住,多余各种异想天开的操作都扔到外部去折腾,不要千万不要插入系统内去制造所谓的强悍和牛逼功能。因为你越牛就等于废码的形成越来越早地出现。

猜你喜欢

转载自www.cnblogs.com/GameDelphi/p/9126147.html