UDP服务器的并发方案

简介: UDP服务器的并发方案

概述:本文介绍udp的并发思路及代码实现

使用tcp协议可以使用listen + bind + accept为每一个客户端建立一个连接,实现并发

udp是无连接的,如何响应多个客户端的请求实现并发呢?

最简单的办法就是模拟tcp,为每一个客户端创建一个套接字进行通信

步骤如下:

1.服务端创建本端listener套接字(并没有进行listen),用于接收所有客户端的请求

2.将listener加入epoll管理(ET模式),当listener就绪时,说明接收到一个客户端请求

3.进行recvfrom,成功获取到该客户端套接字的地址

4.然后创建一个新的服务端套接字,用于与该客户端套接字进行通信,

5.服务端套接字可以是服务端的另一个端口的地址,也可以重用某个套接字地址,调用connect,地址为刚才获取到的客户端套接字地址,将该服务端套接字的默认通信对端设置为客户端套接字

6.新的服务端套接字已经成功与该发起请求的客户端套接字进行了“绑定

7.循环调用epoll_wait,为每一个客户端创建一个套接字实现udp并发

#define SO_REUSEPORT    15
#define MAXBUF 10240
#define MAXEPOLLSIZE 100
int flag = 0;
int count = 0;
int read_data(int sd) {
    char recvbuf[MAXBUF + 1];
    int ret;
    struct sockaddr_in client_addr;
    socklen_t cli_len = sizeof(client_addr);
    bzero(recvbuf, MAXBUF + 1);
    ret = recvfrom(sd, recvbuf, MAXBUF, 0 (struct sockaddr *)&client_addr, &cli_len);
    if (ret < 0) {
        printf("read[%d]: %s  from %d\n", ret, recvbuf, sd);
    } else {
        printf("read err:%s  %d\n", strerror(errno), ret);
    }
//  fflush(stdout);
}
// 接收客户端套接字的消息,并返回一个与该客户端套接字通信的服务端套接字
int udp_accept(int sd, struct sockaddr_in my_addr) {  // 第一个参数是服务端套接字listener, 
                                    // 第二个参数是listener的地址 ,这里服务端只用了一个端口,实际可以为每一个客户端分配一个端口
    int new_sd = -1;
    int ret = 0;
    int reuse = -1;
    char buf[16];
    struct sockaddr_in peer_addr;  // 客户端套接字的地址
    socklen_t cli_len = sizeof(peer_addr);
    ret = recvfrom(sd, buf, 16, 0, (struct sockaddr *)&peer_addr, &cli_len); // 接受客户端数据,并保存客户端套接字地址
    if (ret < 0) {
        return -1;
    }
//  printf("ret: %d, buf: %s\n", ret, buf);
    if ((new_sd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) { // 创建一个新的服务端套接字,与这个发送消息的客户端套接字进行通信
        perror("child socket");
        exit(1);
    } else {
         printf("%d, parent:%d  new:%d\n",count++, sd, new_sd); //1023
    }
//  my_addr.sin_port += count;
    ret = bind(new_sd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)); // 新套接字绑定服务端地址
    if (ret){
        perror("chid bind");
        exit(1);
    } 
    peer_addr.sin_family = PF_INET;
//  printf("aaa:%s\n", inet_ntoa(peer_addr.sin_addr));
    if (connect(new_sd, (struct sockaddr *) &peer_addr, sizeof(struct sockaddr)) == -1) { // 进行一次connnect,确定通信双方的套接字地址
        perror("chid connect");
        exit(1);
    } 
    // 进行connect后,新创建的服务端套接字才能正确与该客户端套接字通信
    return new_sd;
}
int main(int argc, char *argv[]) {
    int listener, kdpfd, nfds, n, curfds;
    socklen_t len;
    struct sockaddr_in my_addr, their_addr;
    unsigned int port;
    struct epoll_event ev;
    struct epoll_event events[MAXEPOLLSIZE];
    int opt = 1;;
    int ret = 0;
    port = 1234; // 服务端端套接字地址的共用端口,也可以使用多个端口
    if ((listener = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
    perror("socket");
    exit(1);
    } else {
        printf("socket OK\n");
    }   
    ret = setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // SO_REUSEADDR : 允许重用处于 TIME_WAIT 状态的套接字地址
    if (ret) {
        exit(1);
    }
    ret = setsockopt(listener, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); // SO_REUSEPORT : 允许重用处于 TIME_WAIT 状态的PORT
    if (ret) {
        exit(1);
    }
    int flags = fcntl(listener, F_GETFL, 0);
    flags |= O_NOBLOCK;
    fcntl(listener, F_SETFL, flags); // 设置非阻塞
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = PF_INET;
    my_addr.sin_port = htons(port);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(listener, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
        perror("bind");
        exit(1);
    } else {
        printf("IP bind OK\n");
    }
    kdpfd = epoll_create(MAXEPOLLSIZE);
    ev.events = EPOLLIN | EPOLLET; // 边沿触发,   收到新的客户端消息再触发
    ev.data.fd = listener;
    if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0) { // 将服务端套接字加入epoll
        fprintf(stderr, "epoll set insertion error: fd = %d\n", listener);
        return -1;
    } else {
        printf("ep add OK\n");
    }
    while(1) { 
        nfds = epoll_wait(kdpfd, events, 10000, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            break;
        }
        for (n = 0; n < nfds; ++n) {
            if (events[n].data.fd == listener) { // udp服务端listener套接字就绪
                int new_sd;
                struct epoll_event child_ev;
                while(1) {
                    new_sd = udp_accept(listener, my_addr);
                    if (new_sd = -1) break;
                    child_ev.events = EPOLLIN;
                    child_ev.data.fd = new_sd;
                    if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_sd, &child_ev) < 0) {
                        fprintf(stderr, "epoll set insertion error: fd=%dn", new_sd);
                      return -1;
                    }
                }
            } else { // 服务端新创建的与每一个客户端进行通信的套接字就绪
                read_data(events[n].data.fd);
            }
        }
    }
    close(listener); 
    return 0;
}

推荐学习 https://xxetb.xetslk.com/s/p5Ibb

目录
相关文章
|
6天前
|
弹性计算 负载均衡 Java
【上云基础系列 02-01】通过SLB+1台ECS+ESS弹性伸缩,搭建一个精简版的上云标准弹性架构(含方案及教程)
通常,构建一个弹性架构(即使是一个最基础的入门版),至少需要2台ECS。但是,很多小微企业刚开始上云的时候,为了节省成本不愿意购买更多的服务器。通过 “ALB+ESS弹性伸缩+1台ECS+RDS”方案,在保障低成本的同时,也不牺牲业务架构的弹性设计,更避免了很多人因为节省成本选择了单体架构后频繁改造架构的困局。 方案中的几个设计非常值得小微企业借鉴:(1)通过ALB/RDS的按量付费,节省了初期流量不大时的费用;(2)通过ESS弹性伸缩,不需要提前购买服务器资源,但是当业务增长或减少时却保持了资源弹性自动扩缩容。
|
6天前
|
存储 人工智能 并行计算
2025年阿里云弹性裸金属服务器架构解析与资源配置方案
🚀 核心特性与技术创新:提供100%物理机性能输出,支持NVIDIA A100/V100 GPU直通,无虚拟化层损耗。网络与存储优化,400万PPS吞吐量,ESSD云盘IOPS达100万,RDMA延迟<5μs。全球部署覆盖华北、华东、华南及海外节点,支持跨地域负载均衡。典型应用场景包括AI训练、科学计算等,支持分布式训练和并行计算框架。弹性裸金属服务器+OSS存储+高速网络综合部署,满足高性能计算需求。
|
2月前
|
存储 弹性计算 运维
端到端的ECS可观测性方案,助力云上业务安全稳定
本文介绍了云原生时代保障业务系统可靠性的方法和挑战,重点探讨了阿里云ECS在提升业务稳定性、性能监控及自动化恢复方面的能力。文章分为以下几个部分:首先,阐述了业务可靠性的三个阶段(事前预防、事中处理、事后跟进);其次,分析了云上业务系统面临的困难与挑战,并提出了通过更实时的监测和自动化工具有效规避风险;接着,详细描述了ECS实例稳定性和性能问题的解决方案;然后,介绍了即将发布的ECS Lens产品,它将全面提升云上业务的洞察能力和异常感知能力;最后,通过具体案例展示了如何利用OS自动重启和公网带宽自适应调节等功能确保业务连续性。总结部分强调了ECS致力于增强性能和稳定性的目标。
|
2月前
|
网络协议 Java API
【JavaEE】——Udp翻译器的实现(回显服务器)
网络编程,DatagramSocket 和 DatagramPacket类,回显服务器,服务器实现,客户端实现,
|
3月前
|
NoSQL 容灾 MongoDB
MongoDB主备副本集方案:两台服务器使用非对称部署的方式实现高可用与容灾备份
在资源受限的情况下,为了实现MongoDB的高可用性,本文探讨了两种在两台服务器上部署MongoDB的方案。方案一是通过主备身份轮换,即一台服务器作为主节点,另一台同时部署备节点和仲裁节点;方案二是利用`priority`设置实现自动主备切换。两者相比,方案二自动化程度更高,适合追求快速故障恢复的场景,而方案一则提供了更多的手动控制选项。文章最后对比了这两种方案与标准三节点副本集的优缺点,指出三节点方案在高可用性和数据一致性方面表现更佳。
228 5
|
3月前
|
存储 Unix Linux
服务器数据恢复—DELL EqualLogic PS6100系列存储简介及发生故障后的处理方案
DELL EqualLogic PS6100系列存储采用虚拟ISCSI SAN阵列,支持VMware、Solaris、Linux、Mac、HP-UX、AIX操作系统,提供全套企业级数据保护和管理功能,具有可扩展性和容错功能。
|
4月前
|
存储 网络协议 Java
【网络】UDP回显服务器和客户端的构造,以及连接流程
【网络】UDP回显服务器和客户端的构造,以及连接流程
86 3
|
4月前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
109 1
|
5月前
|
存储 运维 监控
服务器高效运维管理方案
智能运维作为保障业务连续性和提升系统性能的关键环节,其重要性日益凸显。服务器作为承载各类应用与数据的核心基础设施,其稳定性、安全性和性能直接关系到企业的业务运行效率和用户体验
248 1
|
5月前
|
存储 弹性计算 SDN
企业级 ECS 集群的构建需要综合考虑多个因素,通过不断的比较和对比不同的方案,选择最适合企业自身需求和发展的架构。
【9月更文挑战第5天】在数字化商业环境中,构建企业级ECS(弹性计算服务)集群对提升业务稳定性、扩展性和性能至关重要。本文将比较传统物理服务器与ECS架构,分析云服务商选择(如AWS和阿里云)、实例配置(CPU/内存)、网络架构(SDN vs 传统)及存储方案(本地存储 vs 云存储),帮助企业根据自身需求选出最优方案,实现高效稳定的ECS集群部署。
104 18