c – 使用UDP与UDP?

我非常熟悉什么输入/输出完成端口涉及到TCP.

但是,如果我例如编码FPS游戏,或任何需要低延迟时间的事情可能是一个交易破坏者 – 我想立即回应玩家提供最好的播放体验,即使在失去一些空间数据的代价走.很明显,我应该使用UDP,除了经常发送坐标更新之外,还应该实现一些半可靠的协议(afaik TCP在UDP中引起丢包,所以我们应该避免混合这两个)来处理诸如聊天消息之类的事件,或者发生丢包可能至关重要的枪声.

假设我的目标是针对MMOFPS游戏的表现,这个游戏允许在一个永恒的世界中遇到数以百计的玩家,除了与枪打架之外,它还允许他们通过聊天消息等进行交流 – 这样的事情实际上是存在的并且运行良好 – 查看PlanetSide 2.

网上的许多文章(例如这些来自msdn)表示重叠的套接字是最好的,而IOCP是神层概念,但是似乎没有区别使用TCP以外的其他协议的情况.

因此,在开发这样的服务器时,几乎没有关于使用I / O技术的可靠信息,我已经看过this,但是这个话题似乎是很有争议的,我也看过this,但是考虑到第一个链接的讨论我不知道是否应该遵循第二个假设,不管我是否应该使用UDP来完成UDP,如果不是,应该使用UDP的最可扩展和高效的I / O概念.

或者也许我只是进行另一个过早的优化,目前还不需要提前思考?

想想在gamedev.stackexchange.com上发布,但这个问题更适用于我认为的通用网络.

我不建议使用这个,但技术上接收UDP数据报的最有效的方式是只是阻止recvfrom(或WSARecvFrom,如果你愿意).当然,你需要一个专门的线程,否则在你阻止的时候会发生很多事情.

除了TCP之外,您没有在协议中内置连接,并且没有定义边框的流.这意味着您会收到每个数据报的发件人的地址,您会收到一个完整的消息或没有.总是.没有例外.
现在,在recvfrom上的阻塞意味着一个上下文切换到内核,一个上下文在收到某个东西时切换回来.由于只有一个数据报可以同时到达导线,因此在飞行中也有几个重叠的读取速度不会更快,这是迄今为止最大的限制因素(CPU时间不是瓶颈!).使用IOCP意味着至少4个上下文切换,两个用于接收,两个用于通知.或者,具有完成回调的重叠接收也不会更好,因为您必须使用NtTestAlert或SleepEx才能运行APC队列,所以再次有至少2个额外的上下文切换(虽然,所有通知只有2个,偶然已经睡了)

然而:
使用IOCP和重叠读取仍然是最好的方法,即使它不是最有效的.完成端口与使用TCP无关,它们也与UDP一样正常工作.只要您使用重叠的读取,您使用什么协议(甚至是网络或磁盘还是其他可执行或可警告的内核对象)无关紧要.
对于延迟或CPU负载也无关紧要,无论您是否为完成端口刻录了额外的几百个周期.我们在这里谈论“纳米”和“毫米”,这是一到一百万的因素.另一方面,完成港口总体上是一个非常舒适,健全,高效的系统.

您可以在没有及时收到ACK的情况下简单地实现重新发送的逻辑(当需要可靠性的形式时,您必须执行此操作),以及keepalive.
对于keepalive,添加一个等待定时器(可能在15或20秒后点火),您每次收到任何东西时重置.如果你的完成端口告诉你这个计时器已经关闭了,你知道连接已经死了.
对于重发,你可以在GetQueuedCompletionStatus上设置一个超时,每次唤醒时,都会发现所有数据包超过那么老,还没有被确认.
整个逻辑发生在一个地方,这是非常好的.它是多才多艺,高效率,难以做错.

您甚至可以在完成端口上阻止多个线程(并且确实有比您的CPU具有核心更多的线程).许多线程听起来像一个不明智的设计,但实际上是最好的事情.

完成端口以先到先后的顺序唤醒N个线程,N为核心数,除非您告诉它执行不同的操作.如果任何这些线程阻塞,另一个线程被唤醒以处理未完成的事件.这意味着在最坏的情况下,一个额外的线程可能在短时间内运行,但这是可以忍受的.在平均情况下,只要有一些工作要做,并且否则处理器使用率接近100%,这是非常好的. LIFO唤醒有利于处理器缓存,并保持线程上下文切换.

这意味着您可以阻止并等待传入​​的数据报并处理它(解密,解压缩,执行逻辑,从磁盘读取任何东西),另一个线程将立即准备好处理下一个微秒可能出现的下一个数据报.您也可以使用具有相同完成端口的重叠磁盘IO.如果您有计算工作(例如AI)可以将其分解为任务,您可以手动发布(PostQueuedCompletionStatus)完成端口上的工作,并且您有一个并行任务计划程序是免费的.所有你需要做的是将OVERLAPPED包装到一个后面有一些额外的数据的结构中,并使用你将会识别的键.不用担心线程同步,它只是神奇的工作(你甚至不需要在你的自定义结构中有一个OVERLAPPED发布你自己的通知,它将适用于你通过的任何结构,但我不喜欢说谎操作系统,你永远都不知道…).

它甚至不管你是否阻止,例如从磁盘读取时.有时这只是发生,你不能帮助.那么什么,一个线程阻塞,但你的系统仍然收到消息并对它做出反应!必要时,完成端口自动从其池中拉出另一个线程.

关于TCP在UDP上引起数据包丢失,这是我倾向于称之为城市神话的东西(尽管它有些正确).这种常见口头禅的方式是误导性的.这可能是一次真实的(有关于这件事情的研究,然而近十年之久),路由器会丢弃UDP来支持TCP,从而导致数据包丢失.不过,现在肯定不是这样.
更真实的观点是,您发送的任何内容都会导致数据包丢失. TCP引起TCP和UDP上的数据包丢失会导致TCP上的数据包丢失,反之亦然,这是正常的情况(顺便说一下,TCP如何实现拥塞控制).如果另一个插头上的电缆“静音”,则路由器通常将转发一个传入的数据包,它将以硬截止时间排队几个数据包(缓冲区通常是故意小的),可选地,它可以应用某种形式的QoS,并且它将简单地,默默地把所有的东西都丢下来.
许多具有相当苛刻的实时要求的应用程序(VoIP,视频流,您的名字)现在使用UDP,并且当它们处理丢失的数据包或两个丢失的数据包时,它们根本不像重要的,重复的数据包丢失.尽管如此,它们在具有大量TCP流量的网络上可以正常工作.我的电话(如数百万人的手机)专用于VoIP,数据通过与互联网流量相同的路由器.没有办法,我可以用TCP来挑衅辍学,无论我尝试多么努力.
从这个日常观察,可以肯定地说,UDP绝对不会有利于TCP.如果有的话,QoS可能会有利于UDP over TCP,但是它绝对不会使它受到影响.
否则,如果您下载了一些DVD ISO文件的大小,那么像开启网站一样,像VoIP这样的服务就会消失.

编辑:
给出一些想法,如何简单的生活与IOCP可以(有些剥离,实用程序功能缺失):

for(;;)
{
    if(GetQueuedCompletionStatus(iocp, &n, &k, (OVERLAPPED**)&o, 100) == 0)
    {
        if(o == 0) // ---> timeout, mark and sweep
        {
            CheckAndResendMarkedDgrams();  // resend those from last pass
            MarkUnackedDgrams();           // mark new ones
        } 
        else
        {   // zero return value but lpOverlapped is not null:
            // this means an error occurred
            HandleError(k, o);
        }
        continue;
    }

    if(n == 0 && k == 0 && o == 0)
    {
        // zero size and zero handle is my termination message
        // re-post, then break, so all threads on the IOCP will
        // one by one wake up and exit in a controlled manner
        PostQueuedCompletionStatus(iocp, 0, 0, 0);
        break;
    }
    else if(n == -1) // my magic value for "execute user task"
    {
        TaskStruct *t = (TaskStruct*)o;
        t->funcptr(t->arg);
    }
    else
    {
        /* received data or finished file I/O, do whatever you do */
    }
}

注意,处理完成消息,用户任务和线程控制的整个逻辑在一个简单的循环中发生,没有模糊的东西,没有复杂的路径,每个线程只执行相同的相同循环.相同的代码适用于1个线程服务1个套接字,或者用于50个服务5,000个套接字的池中的16个线程,10个重叠文件传输以及执行并行计算.

相关文章
相关标签/搜索