有名管道实现简单版聊天功能

编写程序实现多个Client之间通过Server来传递消息从而实现client间的通信功能。要求如下:

1. 服务器创建3个众所周知的命名管道FIFO_1, FIFO_2, FIFO_3, 分别接收用户的注册请求、登录请求和聊天请求,服务器等待任一管道来的请求,收到时立即响应;不同用户不允许使用同一用户名注册,并设置密码。

2. 每个用户都建立一个以自己用户名为名字的命名管道,用户A发送给用户B的信息发送给服务器,然后服务器通过B的专用FIFO发送给用户B

多用户聊天系统框架图

编写两个代码,服务器程序server.c客户端程序client.c

服务器程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <signal.h>

#define MAX_USERS 10           // 最大用户数
#define MAX_USERNAME_LENGTH 20 // 命名最大长度
#define MAX_MESSAGE_LENGTH 256 // 消息最大长度
// 三个公共管道,注册,登录,聊天
#define FIFO_REGISTER "FIFO_1"
#define FIFO_LOGIN "FIFO_2"
#define FIFO_CHAT "FIFO_3"
/*
    结构体:用户
    用户名,用户密码,登陆状态,用户私有管道名
*/
typedef struct
{
    char username[MAX_USERNAME_LENGTH];
    char password[MAX_USERNAME_LENGTH];
    int is_logged_in;
    char user_fifo[MAX_USERNAME_LENGTH];
} User;

User users[MAX_USERS];
int user_count = 0;

/*
    管道不存在时创建管道
*/
void create_fifo(char *fifo_name)
{
    if (access(fifo_name, F_OK) == -1)
    {
        int res = mkfifo(fifo_name, 0666);
        if (res != 0)
        {
            printf("FIFO %s was not created\n", fifo_name);
            exit(EXIT_FAILURE);
        }
    }
}

/*
    注册新用户
*/
void register_user(char *username, char *password, char *user_fifo)
{
    if (user_count < MAX_USERS)
    {
        int is_duplicate = 0;
        for (int i = 0; i < user_count; i++)
        {
            // 查找是否有重复用户名
            if (strcmp(users[i].username, username) == 0)
            {
                is_duplicate = 1;
                break;
            }
        }
        if (is_duplicate) // 有重复用户名,返回1
        {
            
            int users_fd = open(user_fifo, O_WRONLY | O_NONBLOCK);
            int cmp = 1;
            write(users_fd, &cmp, sizeof(cmp));
            close(users_fd);
            printf("repeat of user name!!!\n");
        }
        else // 没有重复用户名,注册成功,返回0
        {
            strcpy(users[user_count].username, username);
            strcpy(users[user_count].password, password);
            strcpy(users[user_count].user_fifo, user_fifo);
            users[user_count].is_logged_in = 0;
            user_count++;

            int users_fd = open(user_fifo, O_WRONLY | O_NONBLOCK);
            int cmp = 0;
            write(users_fd, &cmp, sizeof(cmp));
            close(users_fd);
            printf("User name available.\n");
        }
    }
}
/*
    信号处理函数
*/
void handler(int sig)
{
    unlink(FIFO_REGISTER);
    unlink(FIFO_LOGIN);
    unlink(FIFO_CHAT);
    exit(1);
}

int main()
{
    signal(SIGKILL, handler);
    signal(SIGINT, handler);
    signal(SIGTERM, handler);

    create_fifo(FIFO_REGISTER);
    create_fifo(FIFO_LOGIN);
    create_fifo(FIFO_CHAT);

    int register_fd, login_fd, chat_fd;
    fd_set read_fds;
    int max_fd;
    struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    // 打开有名管道文件描述符
    register_fd = open(FIFO_REGISTER, O_RDONLY);
    login_fd = open(FIFO_LOGIN, O_RDONLY);
    chat_fd = open(FIFO_CHAT, O_RDONLY);

    if (register_fd == -1 || login_fd == -1 || chat_fd == -1)
    {
        perror("Failed to open one or more pipes");
        exit(1);
    }

    // 初始化文件描述符集
    FD_ZERO(&read_fds);

    // 添加管道文件描述符到集合中
    FD_SET(register_fd, &read_fds);
    FD_SET(login_fd, &read_fds);
    FD_SET(chat_fd, &read_fds);

    // 找到最大的文件描述符
    if (register_fd > max_fd)
        max_fd = register_fd;
    if (login_fd > max_fd)
        max_fd = login_fd;
    if (chat_fd > max_fd)
        max_fd = chat_fd;

    while (1)
    {
        // 复制文件描述符集
        // 防止原来的文件描述符集被修改
        fd_set tmp_fds = read_fds;
        if (select(max_fd + 1, &tmp_fds, NULL, NULL, &tv) == -1)
        {
            perror("select");
            exit(1);
        }

        // 检查哪个文件描述符有数据可读

        // 注册
        if (FD_ISSET(register_fd, &tmp_fds))
        {
            char username[MAX_USERNAME_LENGTH];
            char password[MAX_USERNAME_LENGTH];
            char user_fifo[MAX_USERNAME_LENGTH];
            memset(username, 0, sizeof(username));
            memset(password, 0, sizeof(password));
            memset(user_fifo, 0, sizeof(user_fifo));
            int bytes_read1 = read(register_fd, username, sizeof(username));
            int bytes_read2 = read(register_fd, password, sizeof(password));
            int bytes_read3 = read(register_fd, user_fifo, sizeof(user_fifo));
            if (bytes_read1 > 0 && bytes_read2 > 0 && bytes_read3 > 0)
            {
                username[bytes_read1] = '\0';
                password[bytes_read2] = '\0';
                user_fifo[bytes_read3] = '\0';
                printf("\nReceived registration request: \n");
                printf("username: %s\n", username);
                printf("password: %s\n", password);
                printf("user_fifo: %s\n", user_fifo);
                register_user(username, password, user_fifo);
            }
        }
        // 登录
        if (FD_ISSET(login_fd, &tmp_fds))
        {
            char username[MAX_USERNAME_LENGTH];
            char password[MAX_USERNAME_LENGTH];
            memset(username, 0, sizeof(username));
            memset(password, 0, sizeof(password));
            int bytes_read1 = read(login_fd, username, sizeof(username));
            int bytes_read2 = read(login_fd, password, sizeof(password));
            if (bytes_read1 > 0 && bytes_read2 > 0)
            {
                printf("\nReceived login request: \n");
                for (int i = 0; i < user_count; i++)
                {
                    if (strcmp(users[i].username, username) == 0)
                    {
                        users[i].is_logged_in = 1;
                        printf("%s has logged in.\n", username);
                        break;
                    }
                }
            }
        }
        // 聊天
        if (FD_ISSET(chat_fd, &tmp_fds))
        {
            char sender[MAX_USERNAME_LENGTH];
            char receiver[MAX_USERNAME_LENGTH];
            char message[MAX_MESSAGE_LENGTH];
            memset(sender, 0, sizeof(sender));
            memset(receiver, 0, sizeof(receiver));
            memset(message, 0, sizeof(message));

            int bytes_read1 = read(chat_fd, sender, MAX_USERNAME_LENGTH);
            int bytes_read2 = read(chat_fd, receiver, MAX_USERNAME_LENGTH);
            int bytes_read3 = read(chat_fd, message, MAX_MESSAGE_LENGTH);
            if (bytes_read1 > 0 && bytes_read2 > 0 && bytes_read3 > 0)
            {
                // 处理聊天请求的逻辑
                printf("\nReceived chat message: \n");
                printf("sender: %s\n", sender);
                printf("recevier: %s\n", receiver);
                printf("message: %s\n", message);
                for (int i = 0; i < user_count; i++)
                {
                    if (strcmp(users[i].username, receiver) == 0 && users[i].is_logged_in)
                    {
                        // Send the message to the receiver's user FIFO
                        // 发送方,消息
                        int recevier_fd = open(users[i].user_fifo, O_WRONLY);
                        write(recevier_fd, sender, sizeof(sender));
                        write(recevier_fd, message, sizeof(message));
                        close(recevier_fd);
                        break;
                    }
                }
            }
        }
    
    }
    // 关闭文件描述符
    close(register_fd);
    close(login_fd);
    close(chat_fd);

    return 0;
}

客户端程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <signal.h>

#define MAX_USERNAME_LENGTH 20 // 命名最大长度
#define MAX_MESSAGE_LENGTH 256 // 消息最大长度
// 三个公共管道,注册,登录,聊天
#define FIFO_REGISTER "FIFO_1"
#define FIFO_LOGIN "FIFO_2"
#define FIFO_CHAT "FIFO_3"

// 客户端私有管道名
char myfifo[MAX_USERNAME_LENGTH];

/*
    管道不存在时创建管道
*/
void create_fifo(char *fifo_name)
{
    if (access(fifo_name, F_OK) == -1)
    {
        int res = mkfifo(fifo_name, 0666);
        if (res != 0)
        {
            printf("FIFO %s was not created\n", fifo_name);
            exit(EXIT_FAILURE);
        }
    }
}

/*
    信号处理函数
*/
void handler(int sig)
{
    if (access(myfifo, F_OK) != -1)
    {
        unlink(myfifo);
    }
    exit(1);
}

int main()
{
    signal(SIGKILL, handler);
    signal(SIGINT, handler);
    signal(SIGTERM, handler);

    fd_set read_fds;
    struct timeval timeout;
    int is_login = 0;
    int my_fd = -1;
    int max_fd;

    int register_fd = open(FIFO_REGISTER, O_WRONLY);
    int login_fd = open(FIFO_LOGIN, O_WRONLY);
    int chat_fd = open(FIFO_CHAT, O_WRONLY);
    printf("Welcome to the chat application.\n");

    char username[MAX_USERNAME_LENGTH];       // 用户名
    char password[MAX_USERNAME_LENGTH];       // 用户密码
    char receiver[MAX_USERNAME_LENGTH];       // 发送消息的接收方
    char message[MAX_MESSAGE_LENGTH];         // 发送消息的内容
    char message_sender[MAX_USERNAME_LENGTH]; // 接收消息的发送方
    char my_message[MAX_MESSAGE_LENGTH];      // 接收消息的内容

    memset(username, 0, sizeof(username));
    memset(password, 0, sizeof(password));
    memset(receiver, 0, sizeof(receiver));
    memset(message, 0, sizeof(message));
    memset(message_sender, 0, sizeof(message_sender));
    memset(my_message, 0, sizeof(my_message));

    // 先注册
    printf("First register a user.\n");
    while (1)
    {
        printf("Enter your username: \n");
        fgets(username, MAX_USERNAME_LENGTH, stdin);
        username[strcspn(username, "\n")] = '\0'; // Remove newline
        printf("Enter your password: \n");
        fgets(password, MAX_USERNAME_LENGTH, stdin);
        password[strcspn(password, "\n")] = '\0'; // Remove newline
        // 管道添加进程号
        sprintf(myfifo, "%s%d", username, getpid());
        create_fifo(myfifo);

        write(register_fd, username, sizeof(username));
        write(register_fd, password, sizeof(password));
        write(register_fd, myfifo, sizeof(myfifo));

        my_fd = open(myfifo, O_RDONLY);

        // 等待服务器响应,重复返回0,不重复返回1
        int flag = -1;
        read(my_fd, &flag, sizeof(flag));

        if (flag == 1)
        {
            // 重复
            printf("The username already exists, please re-enter\n");
            // 初始化,重新输入信息和创建管道
            close(my_fd);
            memset(username, 0, sizeof(username));
            memset(password, 0, sizeof(password));
            if (access(myfifo, F_OK) != -1)
            {
                unlink(myfifo);
            }
            memset(myfifo, 0, sizeof(myfifo));
        }
        else if (flag == 0)
        {
            printf("User registration successful\n");
            break;
        }
        else
        {
            printf("Error!!!\n");
        }
    }

    // 然后登录
    printf("Now you can log in to your user.\n");
    while (1)
    {
        char tmp_username[MAX_USERNAME_LENGTH];
        char tmp_password[MAX_USERNAME_LENGTH];
        printf("Enter your username: \n");
        fgets(tmp_username, MAX_USERNAME_LENGTH, stdin);
        tmp_username[strcspn(tmp_username, "\n")] = '\0'; // Remove newline
        printf("Enter your password: \n");
        fgets(tmp_password, MAX_USERNAME_LENGTH, stdin);
        tmp_password[strcspn(tmp_password, "\n")] = '\0'; // Remove newline
        if (strcmp(tmp_username, username) == 0 && strcmp(tmp_password, password) == 0)
        {
            write(login_fd, tmp_username, sizeof(tmp_username));
            write(login_fd, tmp_password, sizeof(tmp_password));
            break;
        }
        else
        {
            printf("Incorrect username or password, please re-enter.\n");
        }
    }

    while (1)
    {
        int choice; // 选择的操作
        printf("\n1. Send Message\n2. View my messages\n3. Exit\nEnter your choice: ");
        scanf("%d", &choice);
        getchar();

        if (choice == 1) // 聊天
        {
            printf("\nEnter receiver's username: \n");
            fgets(receiver, MAX_USERNAME_LENGTH, stdin);
            receiver[strcspn(receiver, "\n")] = '\0'; // Remove newline
            printf("Enter your message: \n");
            fgets(message, MAX_USERNAME_LENGTH, stdin);
            message[strcspn(message, "\n")] = '\0'; // Remove newline
            write(chat_fd, username, sizeof(username));
            write(chat_fd, receiver, sizeof(receiver));
            write(chat_fd, message, sizeof(message));
        }
        else if (choice == 2) // 查看消息
        {
            int is_message = 0; // 如果没有消息,输出提示
            while (1)
            {
                int bytes_read_sender = read(my_fd, message_sender, sizeof(message_sender));
                int bytes_read_message = read(my_fd, my_message, sizeof(my_message));
                if (bytes_read_sender > 0 && bytes_read_message > 0)
                {
                    if (!is_message)
                    {
                        printf("\nHere is your message:\n");
                    }
                    is_message = 1;
                    printf("From %s: %s\n", message_sender, my_message);
                    memset(message_sender, 0, sizeof(message_sender));
                    memset(my_message, 0, sizeof(my_message));
                }
                else
                {
                    if (!is_message)
                        printf("No more news\n");
                    break;
                }
            }
        }
        else if (choice == 3) // 退出
        {
            printf("Exiting the chat application.\n");
            break;
        }
        else // 错误选项
        {
            printf("Invalid choice. Please try again.\n");
        }

        memset(message, 0, sizeof(message));
        memset(receiver, 0, sizeof(receiver));
    }

    // 删除私有命名管道
    if (access(myfifo, F_OK) != -1)
    {
        unlink(myfifo);
    }
    // 关闭文件描述符
    close(register_fd);
    close(login_fd);
    close(chat_fd);
    close(my_fd);
    return 0;
}

简单分析:

服务器端代码:

1. 引入必要的头文件,包括标准C库、文件操作、进程控制、信号处理和自定义头文件。定义常量,包括最大用户数、用户名和消息的最大长度以及三个公共FIFO名称

2. 定义用户结构体,用于存储用户信息,包括用户名、密码、登录状态和用户私有FIFO名称。user_count变量用来记录服务器中有多少个用户

3. 设置信号处理函数,用于在服务器退出时删除FIFO。

4. 创建`create_fifo`函数,用于检查管道是否存在,如果不存在则创建它。

5. 创建`register_user`函数,用于注册新用户。它会检查是否有重复的用户名,如果没有则将用户信息添加到`users`数组中,并向客户端返回注册结果。

6. 使用`select`函数来监听这些FIFO的读操作,并等待客户端的请求。

7. 在循环中持续监听客户端请求,直到服务器退出。

        处理注册请求:从注册FIFO中读取用户名、密码和用户私有FIFO名称,然后调用`register_user`函数来处理注册逻辑。

        处理登录请求:从登录FIFO中读取用户名,然后标记相应用户的登录状态。

        处理聊天请求:从聊天FIFO中读取发送者、接收者和消息,然后将消息发送给接收者的私有FIFO。

客户端代码:

1. 与服务端代码类似。引入必要的头文件,包括标准C库、文件操作、进程控制、信号处理和自定义头文件。定义常量,包括最大用户名和消息长度以及三个公共FIFO名称。创建`create_fifo`函数,用于检查管道是否存在,如果不存在则创建它。设置信号处理函数,用于在客户端退出时删除客户端私有FIFO。

2. 定义客户端私有FIFO名称`myfifo`,该名称基于客户端的用户名和进程ID。防止出现重名管道。

3. 在`main`函数中,先对SIGKILL和SIGTERM、SIGINT注册信号处理函数handler。然后打开三个公共FIFO(注册、登录和聊天)的写文件描述符。

4. 首先,客户端需要注册用户。用户需要提供用户名和密码,然后创建一个私有FIFO。管道名为用户名+当前进程号。客户端向服务器发送注册请求,包括用户名、密码和私有FIFO名称。

5. 客户端等待服务器的响应,如果用户名已存在,则需要重新输入;如果注册成功,则客户端可以继续登录。

6. 客户端输入用户名和密码进行登录,如果与服务器端匹配,客户端向服务器发送登录请求。

7. 客户端进入主循环,等待用户选择操作(发送消息、查看消息、退出)。

8. 发送消息:客户端输入接收者和消息,然后将消息发送给服务器,服务器会将消息传递给接收者的私有FIFO。

9. 查看消息:客户端检查自己的私有FIFO以查看是否有消息,并显示接收到的消息。

10. 退出:客户端退出应用程序。然后删除客户端私有FIFO,关闭文件描述符。

这两个代码通过三个公共管道实现了用户的注册、登录和聊天功能。服务器端通过`select`函数监听三个管道的输入,根据输入的不同类型进行相应的处理。客户端通过向服务器端发送不同的请求,实现了注册、登录和聊天的功能。整个聊天应用使用了多进程通信的方式,通过管道传递消息。

运行示例:

注册

注册成功情况

注册失败情况

登录

登录成功情况

登录失败情况

聊天

用户A发送消息给用户B

总结:

程序还有许多可以改善的地方,例如加入多线程使客户端接收的消息可以实时显示,不需要去查看才显示。在未来学习了多线程相关知识后,我将进一步改进我的程序。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值