C语言实现 基于poll的 多路复用 TCP echo 服务器模型

设计灵感:

1 单线程io多路复用服务端

2 使用poll实现

3 将server_sockfd client_sockfd 设为非阻塞,实现最大io效率

4 使用套接字选项SO_REUSEADDR 用于测试环境调试

5 将server_sockfd 和每一个有效的client_sockfd 都设为poll的监控事件

6 有客户端关闭连接时,自动从数组中删除,并调整相应的count值

功能:

可实现多客户端同时连接,echo收发无感

注意事项:

1 运行环境 unix-like gnu_c 

2 开启服务端后 可无限开启客户端

3 赠送对比用双循环阻塞服务端

4 赠送测试用客户端

poll多路复用服务端:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/poll.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50013
// 设置非阻塞用函数
void set_non_blocking(int sockfd)
{// 获取默认flagsint flags = fcntl(sockfd, F_GETFL, 0);if (flags == -1){perror("fcntl F_GETFL");}// 位运算 加上O_NONBLOCKflags |= O_NONBLOCK;// 设置 flagsif (fcntl(sockfd, F_SETFL, flags) == -1){perror("fcntl F_SETFL");}
}
int main()
{int server_sockfd, client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);socklen_t server_sockaddr_len = sizeof(server_sockaddr);ssize_t send_bytes, recv_bytes;char send_buf[1024] = "How can I he53453oday ?";char recv_buf[1024] = {0};// 循环用i,j,计数用count:pollfds[count]中每增加一个元素 count++int i = 0, j = 0, count = 0;// 系统允许的单进程 最大fd数量 1024576long max_fds = sysconf(_SC_OPEN_MAX);// 创建一个最大fd数量的数组,并将所有元素的所有字段设为0struct pollfd *pollfds = calloc(max_fds, sizeof(struct pollfd));if (pollfds == NULL){perror("calloc");}// 将每个元素的fd设为-1,表示无效for (i = 0; i < max_fds; i++){pollfds[i].fd = -1;}// 又名 listen socket 用于监听请求,ipv4 tcpserver_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd == -1){perror("socket");}// 为什么要将server_sockfd设为非阻塞?// server_sockfd相关的accept被poll监视,只有读就绪(有连接到来时)才回调用accept// 这意味着accept通常不会阻塞,但是在极端情况下,客户端连接有迅速关闭,则accept有可能阻塞set_non_blocking(server_sockfd);// SO_REUSEADDR 地址端口复用,任意时刻只有一个socket能监听,主要用于程序重启// SO_REUSEPORT 地址端口复用,同时监听,自动负载均衡int optval = 1;if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1){perror("setsockopt");}// 设置协议server_sockaddr.sin_family = AF_INET;// 设置转换端口server_sockaddr.sin_port = htons(SERVER_PORT);// 设置转换ip地址if (inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr) == -1){perror("inet_pton");}// 绑定if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len) == -1){perror("bind");}// 监听,调整了最大等待数量为1024if (listen(server_sockfd, 1024) == -1){perror("listen");}// 将server_sockfd加入POLLIN监听事件pollfds[0].fd = server_sockfd;pollfds[0].events = POLLIN;// count现在是0,++后变成1,遍历i<count,也就是判断pollfds[0]// 但是pollfds[0]读就绪后,主要执行逻辑是accept,因此单独判断count++;printf("server start...\n");while (1){// 如果没有事件真实发生,程序将阻塞在此,而不是一味地循环int r = poll(pollfds, count, -1);// 至少有一个revent被设置if (r > 0){// 如果有连接请求if (pollfds[0].revents & POLLIN){// 就连接client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);if (client_sockfd == -1){perror("accept");}// 将返回的client_sockfd设为非阻塞set_non_blocking(client_sockfd);// 如果代码首次到这,count就是1,pollfds[1]被填充,符合逻辑pollfds[count].fd = client_sockfd;// 同时监控读写pollfds[count].events = POLLIN | POLLOUT;// 向后挪一位count++;}// 首次加入监控事件的accept返回的client_sockfd不会在本次循环中被poll监控,需要等到下次循环for (i = 1; i < count; i++){// 从索引1开始(索引0用于accept已单独处理),如果被监控的fd读就绪if (pollfds[i].revents & POLLIN){// 读recv_bytes = recv(pollfds[i].fd, recv_buf, sizeof(recv_buf), 0);if (recv_bytes == -1){perror("recv");}// 如果客户端关闭了连接if (recv_bytes == 0){printf("close by peer\n");// 服务器也关闭连接close(pollfds[i].fd);// 1 将后面的fd往前挪for (j = i; j < count - 1; j++){pollfds[j] = pollfds[j + 1];}// 2 最后一个fd是一个无效的副本 手动重置pollfds[count - 1].fd = -1;pollfds[count - 1].events = 0;pollfds[count - 1].revents = 0;// 3 count应该减1count--;}// 收到了数据if (recv_bytes > 0){// 打印收到的数据printf("%s\n", recv_buf);// 同时,如果这个fd是写就绪的if (pollfds[i].revents & POLLOUT){// 写send_bytes = send(pollfds[i].fd, send_buf, strlen(send_buf), 0);if (send_bytes == -1){perror("send");}}}}}}if (r == -1){perror("poll");}}// 关闭server_sockfd,代码不可达close(server_sockfd);// 释放动态分配的内存,代码不可达free(pollfds);return 0;
}

对比用双循环阻塞服务端:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50013int main()
{int server_sockfd, client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);ssize_t send_bytes, recv_bytes;char send_buf[1024] = "How can I help you today ?";char recv_buf[1024] = {0};server_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd == -1){perror("socket");}int optval = 1;setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));server_sockaddr.sin_family = AF_INET;inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);server_sockaddr.sin_port = htons(SERVER_PORT);if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1){perror("bind");}if (listen(server_sockfd, 16) == -1){perror("listen");}printf("server start...\n");while (1){client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);if (client_sockfd == -1){perror("accept");}while (1){recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);if (recv_bytes == -1){perror("recv");}else if (recv_bytes == 0){printf("closed by peer\n");break;}else{printf("%s\n", recv_buf);}send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);if (send_bytes == -1){perror("send");}}}close(server_sockfd);return 0;
}

测试用客户端:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50013
int set_non_blocking(int sockfd)
{int flags = fcntl(sockfd, F_GETFL, 0);if (flags == -1){perror("fcntl(F_GETFL)");}flags |= O_NONBLOCK;if (fcntl(sockfd, F_SETFL, flags) == -1){perror("fcntl(F_SETFL)");}return 0;
}
int main()
{int client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);ssize_t send_bytes, recv_bytes;char send_buf[1024] = {0};char recv_buf[1024] = {0};client_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (client_sockfd == -1){perror("socket");}//set_non_blocking(client_sockfd);inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);server_sockaddr.sin_port = htons(SERVER_PORT);server_sockaddr.sin_family = AF_INET;if (connect(client_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1){perror("connect");}getsockname(client_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);snprintf(send_buf, sizeof(send_buf), "%u:he###llo s???ver !!!",ntohs(client_sockaddr.sin_port));while (1){send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);if (send_bytes == -1){perror("send");}recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);if (recv_bytes == -1){perror("recv");}printf("%s\n", recv_buf);sleep(2);}close(client_sockfd);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/786435.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python请求示例京东、淘宝商品数据(属性详情,sku价格页面上的数据抓取)

抓取京东、淘宝等电商平台的商品数据是一个复杂且需要谨慎处理的任务&#xff0c;因为这些平台通常会有反爬虫机制&#xff0c;并且页面结构也可能经常变化。以下是一个简化的Python请求示例&#xff0c;展示如何发起HTTP请求来获取页面内容&#xff0c;但这仅作为起点&#xf…

vue2+elementUi的两个el-date-picker日期组件进行联动

vue2elementUi的两个el-date-picker日期组件进行联动 <template><el-form><el-form-item label"起始日期"><el-date-picker v-model"form.startTime" change"startTimeChange" :picker-options"startTimePickerOption…

提升LLM效果的几种简单方法

其实这个文章想写很久了&#xff0c;最近一直在做大模型相关的产品&#xff0c;经过和团队成员一段时间的摸索&#xff0c;对大模型知识库做一下相关的认知和总结。希望最终形成一个系列。 对于知识库问答&#xff0c;现在有两种方案&#xff0c;一种基于llamaindex&#xff0…

Git简介

文章目录 Git是什么&#xff1f;安装Git使用git配置个人标识文件状态状态变化 汇总常用命令 Git是什么&#xff1f; Git是我们的代码管理工具&#xff0c;是一个代码仓库&#xff0c;让我们存储代码的。 安装Git 官网&#xff1a;https://git-scm.com/ &#xff08;傻瓜式安…

数据结构-链表的基本操作

前言&#xff1a; 在dotcpp上碰到了一道题&#xff0c;链接放这了&#xff0c;这道题就是让你自己构建一遍链表的创建&#xff0c;插入节点&#xff0c;删除节点&#xff0c;获取节点&#xff0c;输出链表&#xff0c;题目给了几张代码图&#xff0c;不过不用管那些图&#xf…

STM32一个地址未对齐引起的 HardFault 异常

1. 概述 客户在使用 STM32G070 的时候&#xff0c;KEIL MDK 为编译工具&#xff0c;当编译优化选项设置为Level0 的时候&#xff0c;程序会出现 Hard Fault 异常&#xff0c;而当编译优化选项设置为 Level1 的时候&#xff0c;则程序运行正常。表面上看&#xff0c;这似乎是 K…

ansible-tower安装

特别注意&#xff1a;不需要提前安装ansible&#xff0c;因为ansible tower中的setup.sh脚本会下载对应的ansible版本 ansible tower不支持Ubuntu系统,对cenos系统版本也有一定的限制&#xff0c;建议使用centos7.9。 准备一台全新的机器安装&#xff0c;因为ansible tower需要…

ARMv8-A架构下的外部debug模型(external debug)简介

Armv8-A external debug Armv8-A debug模型一&#xff0c;外部调试 External debug 简介二&#xff0c;Debug state2.1 Debug state的进入与退出 三&#xff0c;DAP&#xff0c;Debug Access Port3.1 EDSCR, External Debug Status and Control Register调试状态标识&#xff0…

Java比较器(comparable) (comparator)

1.前言 我们经常使用&#xff1e;,&#xff1c;,&#xff1d;等运算符来比较数与数之间的大小关系&#xff0c;但显然这些运算符并不同样适用于对象.但如果需要比较对象&#xff0c;那么我们应该怎么办呢&#xff1f; 我们可以考虑两种方法 : (1) 自然排序 (2). 定制排序. 2.自…

Docker搭建LNMP环境实战(07):安装nginx

1、模拟应用场景描述 假设我要搭建一个站点&#xff0c;假设虚拟的域名为&#xff1a;api.test.site&#xff0c;利用docker实现nginxphp-fpmmariadb部署。 2、目录结构 2.1、dockers根目录 由于目前的安装是基于Win10VMWareCentOS虚拟机&#xff0c;同时已经安装了VMWareT…

《2023腾讯云容器和函数计算技术实践精选集》--在 K8s 上跑腾讯云 Serverless 函数,打破传统方式造就新变革

目录 目录 前言 《2023腾讯云容器和函数计算技术实践精选集》带来的思考 1、特色亮点 2、阅读体验 3、实用建议 4、整体评价 Serverless 和 K8s 的优势 1、关于Serverless 函数的特点 2、K8s 的特点 腾讯云 Serverless 函数在 K8s 上的应用对企业服务的影响 案例分…

如何将 JavaScript 添加到 HTML 页面

介绍 JavaScript&#xff0c;简称 JS&#xff0c;是一种用于网页开发的编程语言。作为 Web 的核心技术之一&#xff0c;JavaScript 与 HTML 和 CSS 一起用于使网页具有交互性并构建 Web 应用程序。现代 Web 浏览器遵循通用的显示标准&#xff0c;通过内置引擎支持 JavaScript&…

vue3 记录页面滚动条的位置,并在切换路由时存储或者取消

需求&#xff0c;当页面内容超出了浏览器可是屏幕的高度时&#xff0c;页面会出现滚动条。当我们滚动到某个位置时&#xff0c;操作了其他事件或者跳转了路由&#xff0c;再次回来时&#xff0c;希望还在当时滚动的位置。那我们就进行一下操作。 我是利用了会话存储 sessionSto…

工业互联网和云计算有关联吗

工业互联网和云计算有关联吗&#xff1f;是的&#xff0c;工业互联网和云计算之间存在紧密的关联。工业互联网是指利用物联网、云计算、大数据分析等技术手段&#xff0c;将传统工业领域与互联网技术相结合&#xff0c;实现设备、工厂和企业之间的连接和数据交互。 工业互联网…

Linux华为云Hadoop配置环境

手工搭建Hadoop环境&#xff08;Linux&#xff09;_弹性云服务器 ECS_最佳实践 (huaweicloud.com)https://support.huaweicloud.com/bestpractice-ecs/zh-cn_topic_0000001698668477.html?localezh-cn#ZH-CN_TOPIC_0000001698668477__li49001945163110跟着傻瓜式CV即可。 气死…

SSH常见运维总结

1 -bash: ssh: command not found 解决办法&#xff1a;"yum install &#xff0d;y openssh-server openssh-clinets" 2 ssh登录时提示&#xff1a;Read from socket failed: Connection reset by peer. 原因&#xff1a;/etc/ssh/下没有ssh*key*文件 解决&…

目标检测:数据集划分 XML数据集转YOLO标签

文章目录 1、前言&#xff1a;2、生成对应的类名3、xml转为yolo的label形式4、优化代码5、划分数据集6、画目录树7、目标检测系列文章 1、前言&#xff1a; 本文演示如何划分数据集&#xff0c;以及将VOC标注的xml数据转为YOLO标注的txt格式&#xff0c;且生成classes的txt文件…

Ubuntu 大压缩文件解压工具

Ubuntu 大压缩文件解压工具 任务解决 任务 需要解压一个百度网盘上下载的压缩文件&#xff0c;zip格式。 直接右键’提取到此处’&#xff0c;会报错&#xff1a;empty archive 用unzip指令&#xff0c;会报错&#xff1a; Archive: 实验.zip warning [实验.zip]: 12540984…

李笑来-财富自由之路【边读边记】2

2024-04-01 23:50 处理完杂事&#xff0c;已经快12点了&#xff0c;但还是想读完这本书。 昨天晚上看完前8章&#xff0c;对李笑来这个同志有了初步的判断&#xff0c;然后不由得去抖音搜了下这个人的资料&#xff0c;果然关于炒币狂转上百亿一下子就出来了&#xff0c;还有就是…

Nginx 日志输出配置json格式

nginx日志输出配置json格式 nginx服务器日志相关指令主要有两条&#xff1a; (1) 一条是log_format&#xff0c;用来设置日志格式 (2) 另外一条是access_log&#xff0c;用来指定日志文件的存放路径、格式和缓存大小。 log_format指令用来设置日志的记录格式&#xff0c;它的语…