UDP服务器的并发方案

news/2024/9/23 6:39:08/

udp_0">概述:本文介绍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 100int 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 状态的PORTif (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) { // 将服务端套接字加入epollfprintf(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


http://www.ppmy.cn/news/1441108.html

相关文章

K8S 哲学 - deployment -- kubectl【create 、 rollout 、edit、scale、set】

kubectl create kubectl rollout kubectl edit kubectl set kubectl scale 1、创建与配置文件解析 2、deploy 滚动更新 &#xff1a;template 里面的内容改变触发滚动更新 编辑该 deploy 的 配置文件 &#xff0c;加入一个 label 不会触发滚动更新 改变 nginx镜…

AI赋能分层模式,解构未来,智领风潮

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;AI赋能分…

【http】协议/域名/url/请求和响应/状态码/重定向

文章目录 0.应用层协议0.1HTTP协议 1.域名2.DNS3.访问浏览器4.URL搜索特殊字符如#&~ 5.万维网6.http请求和响应的格式6.1HTTP请求格式6.2HTTP响应格式6.3示例6.3模拟HTTP【框架】6.4查看请求或响应的工具FiddlerPostman 7.网页7.0对访问网页的认识7.1wget7.2新的认识7.3GET…

TLV61048非同步升压BOOST转换器输入电压2.6-5.5V输出电流4A输出电压最高15V

推荐原因&#xff1a; 输入电压较低&#xff0c;输出电流可达3.5A SOT23-6封装 批量价格约0.70元 TLV61048引脚 TLV61048引脚功能 7 详细说明 7.1 概述 TLV61048是一款非同步升压转换器&#xff0c;支持高达 15 V 的输出电压和输入范围从 2.61 V 到 5.5 V。该TLV61048集成了…

解决eureka服务注册名报错

解决eureka服务注册名报错 解决eureka服务注册名报错简介正文使用RestTemplate.getForObject( url&#xff0c;Class)方法中&#xff0c;url直接使用服务注册名进行拼接后无法正常远程调用。如下 报错404&#xff0c;说明没访问到我的解决方法&#xff1a;换依赖版本原来的版本…

[蓝桥杯2024]-Reverse:rc4解析(对称密码rc4)

无壳 查看ida 这里应该运行就可以得flag&#xff0c;但是这个程序不能直接点击运行 按照伪代码写exp 完整exp&#xff1a; keylist(gamelab) content[0xB6,0x42,0xB7,0xFC,0xF0,0xA2,0x5E,0xA9,0x3D,0x29,0x36,0x1F,0x54,0x29,0x72,0xA8, 0x63,0x32,0xF2,0x44,0x8B,0x85,0x…

初识ChatGPT

初识ChatGPT AIGC这么火热&#xff0c;了解一下&#xff1f;本文主要通过ChatGPT整理了人工智能和GPT相关的很多概念&#xff0c;看完之后&#xff0c;应该能瞥见人工智能的冰山一角。 参考 GPT-4预示着前端开发的终结&#xff1f;你准备好面对无法预测的技术挑战了吗&#…

Kafka分区机制

Kafka分区机制是Kafka消息队列中的一个重要概念&#xff0c;用于将消息分散存储在多个物理节点上&#xff0c;从而实现高吞吐量和可伸缩性。 Kafka的主题&#xff08;Topic&#xff09;可以分为多个分区&#xff08;Partition&#xff09;&#xff0c;每个分区是一个有序的消息…