分享

UDP高性能并发服务器模型

 好汉勃士 2022-08-10 发布于广东

一、前言:

我们知道UDP服务器在实际生活中用的蛮多的。但是由于设计的原因,UDP服务器默认不支持多线程并发的,本文用以探讨UDP服务器并发的实现。

我们先看下TCP 服务器 和 UDP 服务器的编程模型的差异
在这里插入图片描述
我们可以看到,对于TCP 服务器来说,有一个新的客户端连接的时候,会产生一个新的socket用于和新客户端通信。
而对于UDP来说,服务器只有一个socket。所有的客户端都是通过同一个socket进行通信。
在这里插入图片描述
用下面这张图看更直观一点
在这里插入图片描述
对于TCP,每个客户端都有一个socket,因此我们可以创建线程去处理对应的socket。
那对于UDP呢,只有一个socket,那我们如何做到多线程并发呢?

二、随机端口方式

tftpd 程序采用的是随机端口的方式。也就是说如果有一个新的客户端过来,服务器会再随机绑定一个端口号,生成一个新的socket和客户端通信。流程大概就像下面这种图:
在这里插入图片描述
这种方式由于每个客户端有会有独立的端口号 和 socket。故而我们可以采用多线程的方式去处理。每个线程处理一个socket和端口。
但是这种方式有个弊端!
我们知道,端口号实际上是一个 16位二进制数字表示。而16位二进制数字的最大值是 65535 。也就是说这种方式的UDP并发服务器最多只能并发65535。
但实际上是做不到这么多的,因为有些端口号有其他用途,而且如果UDP服务器把所有端口号都占领了,那对于整个系统来说是一场灾难,因为没有多余的端口号可以用了。
所以这种方式只适用于小型的UDP并发服务器。
那还有其他更好的方式吗?

我们可以采用数据队列的方式去实现

三、数据队列方式

对于UDP服务器,我们先创建以下几个重要线程:
接收线程,这个接收线程只从socket中读取数据,然后筛选分发到对应的数据队列中,不对数据再进一步处理。例如客户端1 的数据,就只会发送到数据队列1,客户端2的数据只会发送到数据队列2. 如果是新的客户端,则创建新的队列。

监听线程,负责查询是否有新的数据队列(也就是说有没有新的客户端发数据过来,因为新的客户端发送的数据,会被放到一个新建的数据队列中去),如果有,取出数据队列,然后创建一个新的线程去处理这个新的数据队列。
数据处理线程,该线程由监听线程创建,用于从数据队列中拿出数据进行处理。
拓扑图如下:
udp并发服务器原理
这样的话,每个客户端有对应一个数据队列,每个数据队列都对应一个线程。故而可以做到多线程并发。

原理我们已经知道了,那代码怎么实现呢?
我们封装出几个跟上面的TCP相似的函数接口。使用这些接口,可以很简单写出一个UDP并发服务器。例如:

        /* 主函数 */
        int main(int argc, char *argv[])
        {
            /* 定义一个listen指针。该结构体是自己定义的 */
            struct listen *_listen;
            /* 初始化socket,这个初始化过程跟普通的UDP初始化 socket套接字一样 */
            sockfd = init_socket(); 

            /*
                开始监听这个socket. 最大的连接数为10,也就是说最多只有10个客户端
                封装好的一个函数,功能有点类似于 TCP协议中的 listen 函数
            */
            server_listen(&sockfd, 10);

            while(1)
            {
                /* 
                获得一个连接。类似于TCP的 accept 函数 
                需要注意的是,如果没有连接, server_accept 函数将进入休眠状态,直到有一个新的客户端数据
                客户端只有在第一次发生数据过来的时候,才会创建一个新的 listen ,并唤醒 server_accept 函数
                之后,这个客户端的所有数据都将发送到 这个新的 listen 的数据队列中。
                所以。通过这个 listen ,我们可以创建一个进程,由该进程去处理这个客户端之后的请求
                这里,listen 有点像 TCP 协议中的 accept 函数新建的 sockfd
                */
                _listen = server_accept();

                /* 
                虽然说 server_accept 会进入休眠,但是仍然会被其它信号唤醒,所以要做个判断
                判断下是否为 NULL 。为 NULL 则说明没有新的连接 
                */
                if(_listen == NULL){
                    continue;
                }

                printf('new client \r\n');
                /* 
                启动一个 listen_phread 线程,并且,由该线程去处理这个连接
                类似于TCP 的fork
                */
                listen_pthread(_listen, listen_phread);
            }
        }

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多