【Linux C IO多路复用】多用户聊天系统
目录
Server-Client
mutiplexingServer
mutiplexingClient
mutiplexing
Server-Client
在Linux系统中,IO多路复用是一种机制,它允许一个进程能够监视多个文件描述符(sockets、pipes等)的可读、可写和异常等事件。这样,一个进程就能够同时等待多个IO操作,而不需要创建多个线程来处理每个IO操作。
常见的IO多路复用函数包括
select
、poll
、epoll
等。这些函数允许程序员编写高效的IO多路复用代码,从而使得单个进程能够同时处理多个IO事件,提高系统的并发性能。使用IO多路复用的好处在于,它可以避免创建大量的线程或进程来处理IO事件,从而减少了系统资源的消耗,并且降低了上下文切换的开销。这对于高性能的网络服务器等应用是非常重要的。
编写程序实现多个Client之间通过Server来传递消息从而实现client间的通信功能。要求如下:
服务器创建3个众所周知的命名管道FIFO_1, FIFO_2, FIFO_3, 分别接收用户的注册请求、登录请求和聊天请求,服务器等待任一管道来的请求,收到时立即响应;不同用户不允许使用同一用户名注册,并设置密码。
每个用户都建立一个以自己用户名为名字的命名管道,用户A发送给用户B的信息发送给服务器,然后服务器通过B的专用FIFO发送给用户B。
基本思路是用select函数实现IO多路复用,使得单个线程能够同时处理多个IO事件。
服务器能够处理多个用户的注册请求、登录请求和聊天请求,不同用户之间可以通过服务器进行通信。
我们编写的代码文件有实现服务器的mutiplexingServer.c,实现客户端的mutiplexingClient.c,以及通信配置的mutiplexing.h。
首先是配置通信的头文件mutiplexing.h,在这里我们定义了服务器众所周知的三个命名管道和规定了服务器可容纳用户的数目。
User结构体存储用户的命名管道信息以及用户名和密码。
Chat结构体存储聊天信息,包括目标用户的命名管道信息和发送者的命名管道信息,还有要发送的聊天信息。
还有一个Response结构体用来存储服务器返回客户端的响应信息,由于操作不一定总是客户端所期望的,所以我们除了正常的响应信息外,还存储了一个ok值,当ok值不为0时,说明操作异常。
接下来我们来看服务器的实现。
首先定义了一个全局变量userNumber来记录当前用户的数量,还有一个users数组存储所有用户的信息。
然后创建三个众所周知的命名管道文件并打开。
指定检查这三个文件描述符并获取最大的文件描述符。
关键用select监听这些文件描述符。
然后发现哪个文件描述符准备好可读的时候就调用我们写好的处理函数来处理。
下面逐个讲解我们写的处理函数。
首先是处理注册请求的函数。
我们拿到用户名判断现有的用户组中是否有相同的用户名,如果有,那么我们向客户端返回该用户名已经存在的信息。
如果没有用户名与它相同,那么我们把这个用户的信息加到现有的用户组里面,并向客户端返回注册成功的消息,并且在服务器端打印该用户注册的信息。
然后看登录处理函数。
首先看看当前用户组里面有没有这个用户,找到的话就比较密码是否相同,相同就向客户端发送登录成功的信息,并在服务器端打印该用户登录的信息;密码不同就发送密码错误的信息;如果没有找到这个用户就发送用户名错误的信息。
最后看聊天处理函数。
首先判断该用户要发生的目标用户存不存在,存在的话就向目标用户发送聊天信息,并向发送者反馈信息发送成功,如果目标用户不存在,向发送者反馈该用户不存在。
再看客户端实现。
主函数是创建命名管道,然后进入功能页面显示函数。
在主功能页面,我们首先提示用户输入r表示注册,输入l表示登录,输入q表示退出。如果是输入l或者r,即注册或者登录,我们就收集用户名和密码然后跳转到相应的请求发送函数,如果收到其他字符则给出输入错误信息并重新展示主功能页面。
然后我们看注册请求发送函数。
打开众所周知的注册命名管道,向其写入我们收集的用户名和密码,等待服务器响应后打印响应信息并返回主页面,因为不管注册的结果如何,都需要返回主页面进行下一步的操作。
然后是登录请求函数。
同样我们打开众所周知的登录命名管道,向其写入收集的用户名和密码,如果登录成功,那么进入聊天页面,否则返回主页面。
最后看聊天请求函数。
先展示功能,提示用户按下r表示接收消息,按下s表示发送消息,按下q表示退出当前页面。
如果是发送消息,那么需要输入发送目标用户的用户名已经要发送的消息并打印服务器返回的发送结果。
如果是接收消息,就从自己的命名管道中读取数据并打印。
如果用户按下的其他按键,那么提示用户按错了,重新展示聊天页面。
现在我们来展示运行结果。
首先编译运行服务器,可以看到服务器已经启动。
再编译运行一个客户端,可以看到功能展示页面。
然后我们输入r注册,输入用户名yemaolin和密码2021155015,可以看到服务器返回的注册成功的信息。
然后我们输入l登录,输入刚刚注册的用户名和密码进行登录,可以看到登录成功。
然后我们再开一个客户端注册一个game101,密码是OpenGL并登录。
然后我们用game101给yemaolin发消息hello, I am game101。
然后我们用yemaolin接收消息。
然后yemaolin再给game101发一条消息I love C++。
同样game101可以收到yemaolin发来的消息。
最后我们可以看一下服务器端打印的消息,这展示了用户的状态。
圆满结束IO多路复用。
这个过程还是比较难顶的,但是结果还是比较美好的。
首先不得不说,IO多路复用真的是美妙。我大二曾经用Java写过多个客户端的聊天程序,但是是用的多线程实现的。如今居然可以用单线程实现多用户访问服务器,真是神奇。当然多路复用只能让服务器同时处理多个用户的请求,轮到客户端本身发送和接受消息的时候,在同一时间,客户端只能选择发送消息或者是接收消息。如果要同时接收和发送的话,那估计只有多线程可以实现了。
这次使用select实现的IO多路复用聊天程序除了能够处理正常的用户注册、用户登录和用户间通信之外,还对一些异常情况做了处理,例如用户重复注册,登录用户名错误或者是密码错误,已经发送消息时目标用户不存在等情况做了处理,让我们的程序更加健壮。
除此之外,为了之后的功能拓展以及便于修改,我们将每个功能模块化,并将一些基本的配置消息单独写在一个头文件,这为我们之后的进一步完善做了比较坚实的基础。
mutiplexingServer
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "mutiplexing.h"
#include <sys/select.h>int userNumber = 0; // 当前用户量
User users[UserCapacity];void registerHandler(User*user) {char message[BuffSize];int ok = 0;for (int i = 0; i < userNumber; i++) {if (strcmp(user->username, users[i].username) == 0) {sprintf(message, "The username has already exited!");ok = -1;break;}}if (ok == 0) {strcpy(users[userNumber].username, user->username);strcpy(users[userNumber].password, user->password);sprintf(message, "Register succeed!");printf("User %s register\n",user->username);userNumber++;}int fd = open(user->fifo, O_RDWR | O_NONBLOCK);write(fd, message, BuffSize);
}void loginHandler(User*user) {Response response;response.ok = -1;for (int i = 0; i < userNumber; i++) {if (strcmp(user->username, users[i].username) == 0) {if (strcmp(user->password, users[i].password) == 0) {strcpy(users[i].fifo, user->fifo);sprintf(response.message, "Login succeed!");printf("User %s login\n",user->username);response.ok = 0;break;} else {sprintf(response.message, "Wrong password!");response.ok=-2;break;}}}if (response.ok == -1) {sprintf(response.message, "Wrong username!");}int fd = open(user->fifo, O_RDWR | O_NONBLOCK);write(fd, &response, sizeof(Response));
}void chatHandler(Chat*chat) {char message[BuffSize];int ok = -1;for (int i = 0; i < userNumber; i++) {if (strcmp(chat->targetUser, users[i].username) == 0) {int fd = open(users[i].fifo, O_RDWR | O_NONBLOCK);write(fd, chat->message, BuffSize);sprintf(message, "Send succeed!");ok = 0;break;}}if (ok == -1) {sprintf(message, "User %s does not exit!", chat->targetUser);}int fd = open(chat->fifo, O_RDWR | O_NONBLOCK);write(fd, message, BuffSize);
}int main() {fd_set fds, read_fds; // 文件描述符集合int max_fd;int register_fd, login_fd, chat_fd;// 创建或打开FIFO文件mkfifo(Register_FIFO, 0777);mkfifo(Login_FIFO, 0777);mkfifo(Chat_FIFO, 0777);// 打开FIFO文件 O_RDWR 可读写 O_NONBLOCK 非阻塞register_fd = open(Register_FIFO, O_RDWR | O_NONBLOCK);login_fd = open(Login_FIFO, O_RDWR | O_NONBLOCK);chat_fd = open(Chat_FIFO, O_RDWR | O_NONBLOCK);// 指定要检查的文件描述符FD_ZERO(&fds);FD_SET(register_fd, &fds);FD_SET(login_fd, &fds);FD_SET(chat_fd, &fds);// 获取最大文件描述符max_fd = register_fd > login_fd ? register_fd : login_fd;max_fd = max_fd > chat_fd ? max_fd : chat_fd;printf("MutiplexingServer Listening\n");while (1) {read_fds = fds;User registerData;User loginData;Chat chatData;// 使用select监听文件描述符if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1) {perror("select");exit(EXIT_FAILURE);}// 检查哪些文件描述符已经准备好if (FD_ISSET(register_fd, &read_fds)) {// 处理注册read(register_fd, ®isterData, sizeof(User));registerHandler(®isterData);}if (FD_ISSET(login_fd, &read_fds)) {// 处理登录read(login_fd, &loginData, sizeof(User));loginHandler(&loginData);}if (FD_ISSET(chat_fd, &read_fds)) {// 处理聊天read(chat_fd, &chatData, sizeof(Chat));chatHandler(&chatData);}}
}
mutiplexingClient
#include<unistd.h>
#include<stdio.h>
#include <stdlib.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
#include"mutiplexing.h"void showPage(User*user);void registerClient(User*user){int register_fd = open(Register_FIFO, O_RDWR | O_NONBLOCK);write(register_fd, user, sizeof(User));int fd=open(user->fifo,O_RDWR | O_NONBLOCK);char message[BuffSize];while(1){int result= read(fd,message,BuffSize);if(result>0){printf("%s\n",message);break;}}showPage(user);
}
void chatClient(User*user){char what;printf("Chat Page:\npress r for receive | s for send | q for quit\n");while((what=getchar())=='\n'){}if (what == 'q') {exit(0);} else if(what =='s') { // 发送信息Chat chat;strcpy(chat.fifo,user->fifo);printf("send username: ");scanf("%s",chat.targetUser);getchar();printf("send data: ");scanf("%[^\n]",chat.message);int chat_fd=open(Chat_FIFO,O_RDWR | O_NONBLOCK);write(chat_fd,&chat,sizeof(Chat));// 等待服务器响应int fd=open(user->fifo,O_RDWR | O_NONBLOCK);char message[BuffSize];while(1){int result= read(fd,message,BuffSize);if(result>0){printf("%s\n",message);break;}}} else if(what=='r'){ //接收消息int fd=open(user->fifo,O_RDWR | O_NONBLOCK);char message[BuffSize];while(1){int result= read(fd,message,BuffSize);if(result>0){printf("%s\n",message);break;}}}else{printf("Wrong press key, please try again!\n");}chatClient(user);
}
void loginClient(User*user){int login_fd = open(Login_FIFO, O_RDWR | O_NONBLOCK);write(login_fd, user, sizeof(User));int fd=open(user->fifo,O_RDWR | O_NONBLOCK);Response response;while(1){int result= read(fd,&response,sizeof(Response));if(result>0){printf("%s\n",response.message);break;}}if(response.ok!=0){ // 登录失败showPage(user);}else{chatClient(user);}
}void showPage(User*user){char what;printf("Chat Client:\npress r for register | l for login | q for quit\n");while((what=getchar())=='\n'){}if (what == 'q') {exit(0);} else if(what=='r'||what=='l') {printf("username: ");scanf("%s", user->username);printf("password: ");scanf("%s", user->password);}else{printf("Wrong press key, please try again!\n");showPage(user);}if (what == 'r') {registerClient(user);}else if(what=='l'){loginClient(user);}
}
int main() {User user;sprintf(user.fifo, "client_fifo/client_fifo%d", getpid());mkfifo(user.fifo, 0777);showPage(&user);exit(0);
}
mutiplexing
#ifndef SYSTEMPROGRAM_MESSAGE_H
#define SYSTEMPROGRAM_MESSAGE_H
#define Register_FIFO "register_fifo" // 注册
#define Login_FIFO "login_fifo" // 登录
#define Chat_FIFO "chat_fifo" // 聊天
#define BuffSize 100
#define UserCapacity 10 // 可容纳用户量
typedef struct {char fifo[BuffSize]; //client's FIFO namechar username[20];char password[20];
} User;
typedef struct {char fifo[BuffSize]; //client's FIFO namechar targetUser[20]; // 向谁发送信息char message[BuffSize];
} Chat;
typedef struct{int ok;char message[BuffSize];
}Response;
#endif //SYSTEMPROGRAM_MESSAGE_H
相关文章:

【Linux C IO多路复用】多用户聊天系统
目录 Server-Client mutiplexingServer mutiplexingClient mutiplexing Server-Client 在Linux系统中,IO多路复用是一种机制,它允许一个进程能够监视多个文件描述符(sockets、pipes等)的可读、可写和异常等事件。这样…...

JSON——数组语法
一段JSON可能是以 ”{“ 开头 也可能仅包含一段JSON数组 如下 [ { "name" : "hello,world"}, {"name" : "SB JSON”}, {“name” : "SB互联网房地产CNM“}, ] 瞧,蛋疼不...CJSON过来还是得搜下网…...

运营商大数据精准获客:我们提供精准客源渠道的最大资源体?
运营商大数据精准营销 谈起精准获客,竞争对手永远是为我们提供精准客源渠道的最大资源体! 最新的获客方式,就是从竞争对手的手中把他们的精准客户资源变为自己的。 今年最火的运营商大数据精准营销是拒绝传统营销方式的烧钱推广࿰…...
表象变换与矩阵元
表象变换 一维粒子哈密顿量 表象中的矩阵元 态的表象变换 不难证明 算符的表象变换 坐标表象 Non-denumerable basis...
vue乾坤微前端项目
1、主应用 安装乾坤 npm i qiankun -S 注册微应用并启动: import { registerMicroApps, start } from qiankun;//设置两个微应用 registerMicroApps([{name: vue1, //要跟package.json中的name保持一致entry: //localhost:8081, //本地就这么写container: #cont…...

大语言模型比武
今年随着 ChatGPT 的流行,并在各个领域有一定程度生产级别的应用。国内外也掀起了一股大语言模型浪潮,各大厂商都推出了自己的大语言模型,阿里推出了 通义千问,腾讯推出了 Hunyuan,亚马逊云推出了 Titan,大…...
王道数据结构第五章二叉树的遍历第13题
目录 解题思路 宏定义 二叉树定义 栈定义 实现函数 测试代码 测试结果...
微服务的发展历程的详细说明及每个阶段主流的架构和组件
微服务的发展历程的详细说明及每个阶段主流的架构和组件如下: 一、微服务的发展历程: 起始阶段:这个阶段主要是面向服务的架构(SOA)的兴起。此时,企业开始尝试将单体应用拆分为多个服务,但此时…...

2023年眼镜行业分析(京东眼镜销量数据分析):市场规模同比增长26%,消费需求持续释放
随着我国经济的不断发展,电子产品不断普及,低龄及老龄人口的用眼场景不断增多,不同年龄阶段的人群有不同的视力问题,因此,视力问题人口基数也随之不断加大,由此佩戴眼镜的人群也不断增多。 同时,…...
基础课26——业务流程分析方法论
基础课25中我们提到业务流程分析方法包括以下几种: 价值链分析法:主要是找出或设计出哪些业务能够使得客户满意,实现客户价值最大化的业务流程。要进行价值链分析的时候可以从企业具体的活动进行细分,细分的具体方面可以从生产指…...

【数字图像处理-TUST】实验二-图像噪声生成与滤波降噪
一,题目 读入一幅图像使用两种以上的方法向图像中分别添加噪声输出一幅二值图像,背景为黑色,噪声区域为白色使用三种滤波方法对上述添加了噪声的图像进行降噪处理输出降噪处理后的结果图像 二,实验原理 采用了两种方法添加了噪…...

bilibili快速升满级(使用Docker 容器脚本)
部署bilibili升级运行容器脚本 docker run --name"bili" -v /bili/Logs:/app/Logs -e Ray_DailyTaskConfig__Cron"30 9 * * *" -e Ray_LiveLotteryTaskConfig__Cron"40 9 * * *" -e Ray_UnfollowBatchedTaskConfig__Cron"…...

Android 13.0 Settings主页面去掉FocusRecyclerView相关功能
1.前言 在13.0的系统rom产品定制化开发中,在系统Settings主页面的主菜单中,在测试某些功能的时候,比如开启护眼模式和改变系统密度会在主菜单第一项的网络菜单头部增加 自定义您的设备和设置护眼模式时间安排 等等相关的设置模块 这对于菜单布局显示相当不美观,所以根据系…...

Python(四)字符串
程序员的公众号:源1024,获取更多资料,无加密无套路! 最近整理了一波电子书籍资料,包含《Effective Java中文版 第2版》《深入JAVA虚拟机》,《重构改善既有代码设计》,《MySQL高性能-第3版》&…...
WPF中ElementName与RelativeSource绑定的局限性以及对策
完全来源于十月的寒流,感谢大佬讲解 <Window x:Class"Test_01.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schem…...

基于PHP语言的会员系统搭建(Docker版)
1、操作系统 准备: ubuntu22机器 基础:docker:【精选】Docker微服务-基础_v2/_catalog-CSDN博客 2、安装Docker # Add Dockers official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/…...

文件改名:一次性解决文件名混乱,批量重命名技巧
在日常生活和工作中,我们经常会遇到文件名混乱的问题,例如文件名重复、格式不统一或者文件名错误等。这些问题不仅会给我们带来查找和使用上的困扰,还会影响我们的工作效率。为了解决这些问题,我们可以使用批量重命名技巧…...

app自动化测试——capability 配置参数解析
一、Capability 简介 功能:配置 Appium 会话,告诉 Appium 服务器需要自动化的平台的应用程序 形式:键值对的集合,键对应设置的名称,值对应设置的值 主要分为三部分 公共部分 ios 部分 android 部分 二、Session Appi…...
数仓面经大框架
1.计算机及编程基础: 操作系统:进程、线程等 数据结构:算法题 计算机网络:分层等 Linux:常用的指令 MySQL(重点) Java/Python基础 排序算法(快排、归并等) 2.大数…...

C++ explicit关键字的作用
explicit关键字只针带一个参数的构造函数有效 #include <iostream> using namespace std;class A { public:A(int temp) //普通构造函数{a temp;cout << "普通构造函数: a " << a << endl;}A(const A &temp) //拷贝构造函数{a temp.a…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...