IO多路复用 Linux C Server-Client 多用户聊天系统

news2024/11/24 19:02:49

目录

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 name
    char username[20];
    char password[20];
} User;
typedef struct {
    char fifo[BuffSize]; //client's FIFO name
    char targetUser[20]; // 向谁发送信息
    char message[BuffSize];
} Chat;
typedef struct{
   int ok;
   char message[BuffSize];
}Response;
#endif //SYSTEMPROGRAM_MESSAGE_H

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

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

相关文章

6-会话、过滤器、监听器

6-会话、过滤器、监听器 文章目录 6-会话、过滤器、监听器会话会话概述为什么需要会话管理会话管理实现的手段 Cookie概述使用时效设置路径设置&#xff1a;特定请求才携带cookie SessionHttpSession的概述HttpSession的使用HttpSession的使用-getSession()方法原理HttpSession…

Django快速指南

开始构建 Web 应用程序不仅需要对编码和设计原则有深入的了解&#xff0c;还需要对安全性和性能坚定不移的承诺。在数字化存在至关重要的时代&#xff0c;构建强大而高效的在线平台的能力是一项具有不可估量价值的技能。本教程专门面向网络工匠&#xff0c;即那些希望将技术线索…

提高 bbr 的灵敏性

bbr draft 给出了 MaxBwFilterLen 的定义&#xff1a; MaxBwFilterLen: The filter window length for BBR.MaxBwFilter 2 (representing up to 2 ProbeBW cycles, the current cycle and the previous full cycle). 从 v1 到 v3 版本&#xff0c;该值均只跟状态机而不跟实际&…

#龙迅视频转换IC LT7911D是一款高性能Type-C/DP/EDP 转MIPI®DSI/CSI/LVDS 芯片,适用于VR/显示应用。

1.说明 应用功能&#xff1a;LT7911D适用于DP1.2转MIPIDSI/MIPICSI/LVDS&#xff0c;EDP转MIPIDSI/MIPICSI/LVDS&#xff0c;TYPE-C转MIPIDSI/MIPICSI/LVDS应用方案 分辨率&#xff1a;单PORT高达4K30HZ&#xff0c;双PORT高达4K 60HZ 工作温度范围&#xff1a;−40C to 85C 产…

Discourse 如何在 header 上添加 HTML

虽然现在大部分网站都开始支持使用 CDN 的网站校验了。 但还有些网站在你需要他们提供服务的时候要求使用 header 的 meta 数据校验。 Discourse 是可以轻松的实现上面的功能的。 添加方法 选择你的 Discourse 网站下的自定义。 然后在左侧选择你需要添加的主题。 为了方便…

【EI会议征稿】第三届电气、电力与电网系统国际会议(ICEPGS 2024)

第三届电气、电力与电网系统国际会议&#xff08;ICEPGS 2024&#xff09; 2024 3rd International Conference on Electrical, Power and Grid Systems 第三届电气、电力与电网系统国际会议&#xff08;ICEPGS 2024&#xff09;将于2024年1月26-28日在马来西亚吉隆坡隆重举行…

计算机毕业设计 基于SpringBoot的私人西服定制系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【hexo博客配置】hexo icarus主题配置

配置icarus 步骤一&#xff1a;下载icarus github网址&#xff1a;[hexo-theme-icarus](ppoffice/hexo-theme-icarus: A simple, delicate, and modern theme for the static site generator Hexo. (github.com)) 可以从这个网址上下载zip文件&#xff0c;解压后&#xff0c…

【JAVA学习笔记】65 - 文件类,IO流--节点流、处理流、对象流、转换流、打印流

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter19/src/com/yinhai 文件 一、文件&#xff0c;流 文件,对我们并不陌生&#xff0c;文件是保存数据的地方,比如大家经常使用的word文档,txt文件,excel文件..都是文件。它既可以保存一张图片…

HK WEB3 MONTH Polkadot Hong Kong 火热报名中!

HK Web3 Month 11月除了香港金融科技周外&#xff0c;HK Web3 Month又是一大盛事&#xff0c;从10月29日开始开幕直到11月18日结束。此次将齐聚世界各地的Web3产业从业者、开发者、社群成员和学生来参与本次盛会。除外&#xff0c;超过75位产业知名的讲者与超过50场工作坊将为…

【笔记】回顾JavaWeb结合自身开发的项目——分层解耦与IOC、MySQL简单查询

分层解耦的三层架构 如下图所示是手术训练系统中的实现&#xff1a; 如果你需要从new EmpServiceA()变为new EmpServiceB()&#xff0c;那么必然需要修改Service和Controller层的代码&#xff0c;那么如果我们不new 这个对象呢&#xff1f;是不是就不需要依赖Controller层。 …

Kafka -- 架构、分区、副本

1、Kafka的架构&#xff1a; 1、producer&#xff1a;消息的生产者 2、consumer&#xff1a;消息的消费者 3、broker&#xff1a;kafka集群的服务者&#xff0c;一个broker就是一个节点&#xff0c;主要是负责处理消息的读、写的请求和存储消息。在kafka cluster中包含很多的br…

2023年10月Web3行业月度发展报告区块链篇 |陀螺研究院

10月是加密动荡的一月&#xff0c;围绕比特币现货ETF市场激荡不断&#xff0c;先有Cointelegraph“假消息”搅动市场以致合约遭血洗1.89亿美元&#xff0c;后有灰度、DCTT接二连三释放利好&#xff0c;市场情绪迅速激化&#xff0c;流动性显著提升&#xff0c;USDT 总市值突破8…

全场景数实融合聚焦北京——2023(第六届)行业信息技术应用创新大会隆重召开

2023年11月3日,2023(第六届)行业信息技术应用创新大会在北京裕龙国际酒店隆重举行。中国当前正处于经济转型的关键时期,数字经济的发展对中国新经济和新格局的形成至关重要,而信息技术是数字经济发展过程中必不可少的技术铺垫,企业对信息技术的应用非常依赖业务场景。因此,本届…

SPASS-数据收集及预处理

统计数据的收集 问卷设计 问卷构成 &#xff08;1&#xff09;标题 &#xff08;2&#xff09;导语&#xff08;前言&#xff09; &#xff08;3&#xff09;正文 &#xff08;4&#xff09;结束语 问卷的问题类型 &#xff08;1&#xff09;封闭型问题 &#xff08;2&…

Google关键词挖掘方法

Google关键词挖掘是一种用于搜索引擎优化&#xff08;SEO&#xff09;和市场营销的重要工具。通过挖掘与目标主题相关的关键词&#xff0c;您可以了解潜在客户的需求和兴趣&#xff0c;从而制定更有效的内容策略和广告计划。本文小编将讲讲Google关键词挖掘的方法。 一、Googl…

【Linux系统】文件 / 文件夹权限:chmod

文件 / 文件夹权限&#xff1a;chmod 1.介绍 chmod 命令用于改变文件或目录的访问权限。 改变文件权限 chmod 777 xxx.txt改变文件夹下所有文件的权限 chmod -R 777 *-R 是递归遍历子目录&#xff0c;* 通配符代表要操作的文件。 777 777 777 有 3 3 3 位&#xff0c;最高…

Docker:容器网络互联

Docker&#xff1a;容器网络互联 1. 网络2. 自定义网络 1. 网络 默认情况下&#xff0c;所有容器都是以bridge方式连接到Docker的一个虚拟网桥上&#xff1a; [root172 demo]# docker inspect mysql [root172 demo]# docker inspect dd 在dd容器中ping mysql 但是存在问题&a…

文件分片上传设计

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 现在是接近凌晨了&#xff0c;突然有伙伴给我提到了文件分片上传的事情&#xff0c;我一想&#xff0c;这个我熟…

8086读取键盘输入

文章目录 前言1.从键盘读数据 前言 想过一个问题没有&#xff0c; 8086是如何从键盘中接受输入的&#xff1f; 8086如何将字符在显示器上显示的&#xff1f; 8086如何从磁盘中读取数据的&#xff1f; 上面的问题都是没有操作系统的时候&#xff0c;比如bios的那段代码。 微型…