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

目录

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

武器检测YOLOV8NANO

武器检测&#xff08;匕首&#xff0c;步枪&#xff0c;手枪&#xff09;&#xff0c;采用YOLOV8NANO训练&#xff0c;得到pt模型&#xff0c;然后转换成Onnx模型&#xff0c;供OPENCV DNN调用&#xff0c;支持C,PYTHON,ANDROID。有标注的训练集 武器检测YOLOV8NANO

macOS磁盘分区调整软件--Paragon Camptune X 中文

Paragon Camptune X是一款专为Mac用户设计的强大分区大小调整工具。通过它&#xff0c;用户可以简便地调整Mac硬盘上的分区大小&#xff0c;实现存储空间的高效管理。无论是需要在Mac和Windows系统之间切换的双系统用户&#xff0c;还是有其他特定存储需求的用户&#xff0c;Ca…

提高 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;该值均只跟状态机而不跟实际&…

波形的哪些事

一.静音波形制造(波形卡顿制造) 二.pop波形制造 三.示波器探头设置 四.示波器的差分输入和单端输入的接法不一样 差分的接法&#xff0c;需要配差分探头(如下图)&#xff0c;差分探头的两个脚分别和功放输出通道的两个脚连接 单端的接法&#xff0c;需要单端的探头&#xff0c…

docker项目部署

一.项目说明 hmall&#xff1a;商城的后端代码hmall-portal&#xff1a;商城用户端的前端代码hmall-admin&#xff1a;商城管理端的前端代码 部署的容器及端口说明&#xff1a; 项目容器名端口备注hmallhmall8080黑马商城后端API入口hmall-portalnginx18080黑马商城用户端入…

RT-DETR 应用 BiFPN 结构 | 加权双向特征金字塔网络

模型效率在计算机视觉中变得越来越重要。在本文中,我们系统地研究了目标检测中的神经网络架构设计选择,并提出了几种关键的优化方法来提高效率。首先,我们提出了一种加权双向特征金字塔网络(BiFPN),它可以实现简单快速的多尺度特征融合;其次,我们提出了一种复合缩放方法…

Go:如何在GoLand中引用github.com中的第三方包

本篇博客主要介绍如何在GoLand中引入github.com中的第三方包。具体步骤如下&#xff1a; 正文 (1) 先在GoLand中打开go的工作区目录(即环境变量$GOPATH设置的变量)。如图&#xff1a; 关于工作区目录中的三个子目录: bin: 保存已编译的二进制可执行程序&#xff1b;pkg: 保…

C++ http协议POST body raw 字段向服务器发送请求

环境&#xff1a;ubuntu系统c使用http协议不是很方便&#xff0c;通过curl库我们可以很方便使用http协议&#xff0c;由于我的请求方式比较特殊&#xff0c;在网上没有找到相关的资料&#xff0c;之前使用python实现过一版&#xff0c;但是当设备数量超过100台时&#xff0c;程…

本地生活新赛道-视频号团购怎么做?

目前有在做实体行业的商家一定要看完&#xff0c;只要你进入了这个本地生活新的赛道&#xff0c;那你的生意自然会源源不断&#xff0c;那这个赛道又是什么呢&#xff1f; 这就是十月份刚刚上线的视频号团购项目&#xff0c;开通团购之后&#xff0c;就可以通过发短视频&#…

【GO】项目import第三方的依赖包

目录 一、导入第三方包 1.执行命令 2.查看go环境变量参数 3.查看go.mod文件的变化情况 二、程序里如何import 1. import依赖包 2. 程序编写 本次学习go如果依赖第三方的包&#xff0c;并根据第三方的包提供的接口进行编程&#xff0c;这里需要使用go get命令。下面将go…

Flutter笔记:发布一个模块 scale_design - (移动端)设计师尺寸适配工具

Flutter笔记 发布一个模块scale_design设计师尺寸适配工具与常用组件库 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/a…

Xilinx DDR3 MIG系列——内存基本概念及原理

本节目录 一、内存简介 (1)内存基本存储原理 (2)内存频率 (3)DDR数据预取技术(Prefetch) (4)DDR3工作流程 (5)DDR3控制器的特点 二、内存基本参数 (1)物理Bank (2)逻辑Bank (3)内存芯片容量 (4)行激活命令—tRCD (5)列选通—CL (6)写入延迟—tDQSS (7)行预充电有效周期—tRP (8…

第十三章 Python操作数据库

系列文章目录 第一章 Python 基础知识 第二章 python 字符串处理 第三章 python 数据类型 第四章 python 运算符与流程控制 第五章 python 文件操作 第六章 python 函数 第七章 python 常用内建函数 第八章 python 类(面向对象编程) 第九章 python 异常处理 第十章 python 自定…

『亚马逊云科技产品测评』活动征文|如何搭建低成本亚马逊aws云服务器

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 0. 环境 win10 火狐浏览器 1. 登录 https://aws.amazon.com/cn/ ->…

渗透测试学习day2

文章目录 连接靶机靶机&#xff1a;Fawn 解题过程Task 1Task 2Task 3Task 4Task 5Task 6Task 7Task 8Task 9Task 10Task 11Task 12 总结 连接靶机 详细过程可参考day1 靶机&#xff1a;Fawn 难度&#xff1a;very easy &#xff08;ftp服务的靶机&#xff09; 解题过程 T…

2023/11/7 JAVA学习

ctrl alt t自动重写

linux C++实现线程绑定CPU

前言 嵌入式里面我们会使用到多核的cpu&#xff0c;随着产品芯片性能提升&#xff0c;我们也会有很多功能&#xff0c;以及很多进程产生运行&#xff0c;这个时候我们在任务调度调优的时候&#xff0c;把一些进程绑定到固定cpu运行&#xff0c;下面就来分享一下cpu绑定运行的过…

我在Vscode学OpenCV 图像运算(权重、逻辑运算、掩码、位分解、数字水印)

文章目录 权重 _ 要求两幅图像是相同大小的。[ 1 ] 以数据说话&#xff08; 1&#xff09; 最终&#xff1a;&#xff08; 2 &#xff09;gamma _输出图像的标量值 [ 2 ] 图像的展现力gamma并不等同于增加曝光度&#xff08; 1 &#xff09;gamma100&#xff08; 2 &#xff09…

怎么学编程效率高,编程练习网站编程软件下载,中文编程开发语言工具下载

怎么学编程效率高&#xff0c;编程练习网站编程软件下载&#xff0c;中文编程开发语言工具下载 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的…

viple入门(五)

&#xff08;1&#xff09;自定义活动 自定义活动&#xff0c;用来创建新的组件、服务、函数或者其他代码模块&#xff0c;使用最多的是创建函数。 函数是对一个功能的封装&#xff0c;在调用的时候执行&#xff0c;没有调用的时候则不执行。函数可能有参数&#xff0c;可能没…