【Linux C IO多路复用】多用户聊天系统

目录

Server-Client

mutiplexingServer

mutiplexingClient

mutiplexing


Server-Client

在Linux系统中,IO多路复用是一种机制,它允许一个进程能够监视多个文件描述符(sockets、pipes等)的可读、可写和异常等事件。这样,一个进程就能够同时等待多个IO操作,而不需要创建多个线程来处理每个IO操作。

常见的IO多路复用函数包括selectpollepoll等。这些函数允许程序员编写高效的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, &registerData, sizeof(User));registerHandler(&registerData);}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

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

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

相关文章

Linux学习笔记--高级

Shell概述 1&#xff0c;shell概述 是一个c语言编写的脚本语言&#xff0c;是linux和用户的桥梁&#xff0c;用户输入命令交给shell处理。shell&#xff0c;将相应的操作传递给内核&#xff08;kernel&#xff09;&#xff0c;内核把处理的结果输出给用户 1.1Shell解释器有哪…

redis持久化和Redis事务

一)Redis持久化之RDBredisDataBase: 什么是持久化: 1)持久性:和持久化说的是同一回事&#xff0c;衡量持久性的前提是重启进程或者是重启主机以后数据是否还存在 持久:把数据存储在硬盘上&#xff0c;那么就是持久性 不持久:把数据存储在内存中 2)redis是一个内存级别的数据库&…

el-table给某一行加背景色

数据列表中总价大于100的一行背景色为红色&#xff0c;效果图如下&#xff1a; 代码示例&#xff1a; <template><div id"app"><!-- 测试区域&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&am…

Cordova插件开发三:通过广播实现应用间跨进程通信

文章目录 1.最终效果预览2.数据发送3.插件接受数据4.JS页面中点击获取数据返回1.最终效果预览 场景说明:我们给自来水公司开发了一个h5应用,需要对接第三方厂家支持硬件设备以便于获取到高精度定位数据,之前几篇文件写过,我已经集成过南方测绘RTK和高精度定位模块的设备,厂…

[unity]切换天空盒

序 unity是自带天空盒的&#xff1a; 但有的时候不想用自带的。怎么自定义&#xff1f;如何设置&#xff1f; 官方文档 Unity - Manual: The Lighting window (unity3d.com) 相关窗口的打开方法 天空盒对应的选项 实际操作 从标准材质球到天空盒材质球 新建一个材质球&…

cookie 里面都包含什么属性?

结论先行&#xff1a; Cookie 中除了名称和值外&#xff0c;还有几个比较常见的&#xff0c;例如&#xff1a; Domain 域&#xff1a;指定了 cookie 可以发送到哪些域&#xff0c;只有发送到指定域或其子域的请求才会携带该cookie&#xff1b; Path 路径&#xff1a;指定哪些…

互联网常见职称

1、管理层 CEO – Chief Executive Officer 首席执行官 VP – Vice President 副总裁 HRD – Humen Resource Director 人力资源总监 OD – Operations Director 运营总监 MD – Marketing Director 市场总监 GM – General Manager 总经理 PM – Production Manager 产品…

stm32 Bootloader设计(YModem协议)

stm32 Bootloader设计&#xff08;YModem协议&#xff09; Chapter1 stm32 Bootloader设计(YModem协议)YModem协议&#xff1a;STM32 Bootloader软件设计STM32 Bootloader使用方法准备工作stm32 Bootloader修改&#xff1a;stm32目标板程序.bin偏移地址修改&#xff1a; Chapt…

服务器部署 Nacos 获取不到配置浏览器可以访问

服务器部署 Nacos 获取不到配置浏览器可以访问 &#x1f4d4; 千寻简笔记介绍 千寻简笔记已开源&#xff0c;Gitee与GitHub搜索chihiro-notes&#xff0c;包含笔记源文件.md&#xff0c;以及PDF版本方便阅读&#xff0c;且是用了精美主题&#xff0c;阅读体验更佳&#xff0c…

样式问题解决

1.深度样式选择器 1.vue2中 原生css >>> .el-card__header saas\scss ::v-deep .el-card__header less /deep/ .el-card__header 2.vue3中 :deep() { //styles } ::deep() { //styles } 2.修改element.style样式 3.用户代理样式表样式修改 用户代理样式表是浏…

在现实生活中传感器GV-H130/GV-21的使用

今天&#xff0c;收获了传感器GV-H130/GV-21&#xff0c;调试探头的用法&#xff0c;下面就来看看吧&#xff01;如有不妥欢迎指正&#xff01;&#xff01;&#xff01;&#xff01; 目录 传感器GV-H130/GV-21外观 传感器调试探头 探头与必要准备工作 传感器数值更改调试 …

HTML使用canvas绘制海报(网络图片)

生成前&#xff1a; 生成后&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>媒体参会嘉宾邀请函生成链接</title><link rel"stylesheet" href"https://cdn.jsdelivr.net/npm/vant2.10…

vr地铁消防虚拟逃生自救系统降低财产及人员伤害

无论是在公共场所还是在家中&#xff0c;火灾都是一种常见的突发事件。这往往会严重影响到人们的财产和生命安全。因此&#xff0c;如何预防火灾和安全逃生就成为了非常重要的话题。这款VR模拟火灾疏散逃生系统&#xff0c;帮助人们了解火灾逃生的技巧以及正确的应对方法。 以传…

【操作系统】考研真题攻克与重点知识点剖析 - 第 2 篇:进程与线程

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…

【星海出品】flask 与docker

import os from flask import Flask, request from flask import Response, make_response, jsonify import cv2 import base64 import io import uuid from main import eye ​ app Flask(__name__)​ app.route(/, methods[GET, POST]) # 添加路由blend def upload_file():…

【Agent模型1】MemGPT: Towards LLMs as Operating Systems

论文标题&#xff1a;MemGPT: Towards LLMs as Operating Systems 论文作者&#xff1a;Charles Packer, Vivian Fang, Shishir G. Patil, Kevin Lin, Sarah Wooders, Joseph E. Gonzalez (UC Berkeley) 论文原文&#xff1a;https://arxiv.org/abs/2310.08560 论文出处&#x…

MySQL库的库操作指南

1.创建数据库 一般格式&#xff1a;create database (if not exists) database1_name,database2_name...... 特殊形式&#xff1a; create database charset harset_name collate collate_name 解释&#xff1a; 红色字是用户自己设置的名称charset&#xff1a;指定数据…

Netty入门指南之NIO Buffer详解

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Netty应用专栏_Aomsir的博客-CSDN博客 文章目录 参考文献前言ByteBu…

XSS 跨站点脚本漏洞详解

文章目录 漏洞概述XSS漏洞原理xss漏洞危害xss漏洞验证XSS漏洞分类反射型存储型DOM型 固定会话攻击原理简单xss注入复现 XSS 攻防xss构造方法利用标签符号<>事件响应javascript伪协议其他标签 XSS 变形方式xss防御黑白名单策略输入过滤 案例XSS 盲打 漏洞概述 ​ 跨站点脚…

操作系统 day09(线程)

线程 为什么引入线程 在没引入进程之前&#xff0c;系统中的各个程序只能串行的执行&#xff0c;比如&#xff1a;只能先听歌&#xff0c;再聊QQ。引入进程之后&#xff0c;各个程序可以并发执行&#xff0c;比如&#xff1a;一边听歌&#xff0c;一边聊QQ。但是现在QQ可以一…