编写程序实现多个Client之间通过Server来传递消息从而实现client间的通信功能。要求如下:
1. 服务器创建3个众所周知的命名管道FIFO_1, FIFO_2, FIFO_3, 分别接收用户的注册请求、登录请求和聊天请求,服务器等待任一管道来的请求,收到时立即响应;不同用户不允许使用同一用户名注册,并设置密码。
2. 每个用户都建立一个以自己用户名为名字的命名管道,用户A发送给用户B的信息发送给服务器,然后服务器通过B的专用FIFO发送给用户B。
![](https://img-blog.csdnimg.cn/a55e1c01a6774b68b3f7dfab504ffae0.png)
编写两个代码,服务器程序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
总结:
程序还有许多可以改善的地方,例如加入多线程使客户端接收的消息可以实时显示,不需要去查看才显示。在未来学习了多线程相关知识后,我将进一步改进我的程序。