📜  使用信号和共享内存在两个进程之间聊天应用程序(1)

📅  最后修改于: 2023-12-03 15:06:55.065000             🧑  作者: Mango

使用信号和共享内存在两个进程之间聊天的应用程序

本文将介绍一个使用信号和共享内存在两个进程之间聊天的应用程序。

应用程序架构

该应用程序基于C语言编写,分为两个进程:客户端和服务器端。客户端进程负责发送消息,服务器进程负责接收消息,并将消息广播给所有客户端。进程间通信使用信号和共享内存。

程序实现细节
服务端

服务器进程负责创建和初始化共享内存区域。共享内存区域包含以下信息:

  • 读写指针:服务器进程使用读写指针读取客户端发送的消息,并将消息广播给所有客户端。
  • 消息缓冲区:客户端发送的消息将被存储在消息缓冲区中。

服务器进程使用信号监听客户端的消息。一旦收到消息,服务器进程会将消息存储在消息缓冲区中,并将读写指针更新为最新位置。然后,服务器进程会向所有客户端发送一个信号,告诉它们有新消息可用。

客户端

客户端进程会向服务器进程发送消息,然后等待服务器进程发送新消息的信号。一旦收到信号,客户端进程会使用读写指针读取新消息。

客户端进程在本地维护一张在线用户列表,每当有新用户加入或离开聊天室时,客户端会更新此列表,并将其发送给服务器。

程序代码

以下是客户端和服务器端的源代码实现。需要注意的是,以下代码仅为演示用途,尚未进行严格的错误检查。

// 服务器端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/shm.h>
#include <string.h>

#define MESSAGE_SIZE 50  // 消息缓冲区大小
#define MAX_CLIENTS 10  // 最大客户端数量

struct shared_memory {
  int read_ptr;  // 读指针
  int write_ptr;  // 写指针
  char messages[MAX_CLIENTS][MESSAGE_SIZE];  // 消息缓冲区
};

struct shared_memory* shared_mem_ptr;  // 共享内存指针
int shared_mem_id;  // 共享内存ID

int client_pipe_fds[MAX_CLIENTS][2];  // 客户端管道文件描述符数组

void sighandler(int signum) {
  printf("[SERVER] received signal %d\n", signum);

  // 更新读写指针
  shared_mem_ptr->read_ptr++; // 暂时不考虑溢出问题
  shared_mem_ptr->read_ptr %= MAX_CLIENTS;

  // 向所有客户端广播新消息可用的信号
  for (int i = 0; i < MAX_CLIENTS; i++) {
    if (client_pipe_fds[i][1] != -1) {
      printf("[SERVER] broadcasting signal to client %d\n", i);

      int signal = SIGHUP;
      write(client_pipe_fds[i][1], &signal, sizeof(signal));
    }
  }
}

int main() {
  // 创建共享内存
  shared_mem_id = shmget(IPC_PRIVATE, sizeof(struct shared_memory), IPC_CREAT | 0644);
  if (shared_mem_id == -1) {
    perror("shmget");
    exit(1);
  }

  // 映射共享内存
  shared_mem_ptr = (struct shared_memory*) shmat(shared_mem_id, NULL, 0);
  if (!shared_mem_ptr) {
    perror("shmat");
    exit(1);
  }

  // 初始化共享内存
  for (int i = 0; i < MAX_CLIENTS; i++) {
    for (int j = 0; j < MESSAGE_SIZE; j++) {
      shared_mem_ptr->messages[i][j] = '\0';
    }
  }
  shared_mem_ptr->read_ptr = 0;
  shared_mem_ptr->write_ptr = 0;

  // 创建管道文件描述符数组
  for (int i = 0; i < MAX_CLIENTS; i++) {
    if (pipe(client_pipe_fds[i]) == -1) {
      perror("pipe");
      exit(1);
    }
    // 默认设为关闭状态
    client_pipe_fds[i][0] = -1;
    client_pipe_fds[i][1] = -1;
  }

  // 设置信号处理器
  struct sigaction act;
  act.sa_handler = sighandler;
  sigaction(SIGUSR1, &act, NULL);

  // 监听客户端连接请求
  printf("[SERVER] waiting for clients...\n");
  while (1) {
    int client_fd = accept(0, NULL, NULL);
    if (client_fd == -1) {
      perror("accept");
      exit(1);
    }

    // 找到一个空闲的客户端编号
    int client_id = -1;
    for (int i = 0; i < MAX_CLIENTS; i++) {
      if (client_pipe_fds[i][1] == -1) {
        client_id = i;
        break;
      }
    }
    if (client_id == -1) {
      printf("[SERVER] too many clients, connection rejected\n");
      close(client_fd);
      continue;
    }

    printf("[SERVER] accepted connection from client %d\n", client_id);

    // 将客户端文件描述符添加到管道数组中
    client_pipe_fds[client_id][0] = client_fd;
    client_pipe_fds[client_id][1] = client_fd;

    // 将客户端信息写入共享内存,广播新用户加入的信息
    sprintf(shared_mem_ptr->messages[shared_mem_ptr->write_ptr], "Client %d joined the chat room", client_id);
    shared_mem_ptr->write_ptr++; // 暂时不考虑溢出问题
    shared_mem_ptr->write_ptr %= MAX_CLIENTS;

    sighandler(SIGUSR1);
  }

  // TODO: 处理客户端断开连接的情况
}
// 客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/shm.h>
#include <string.h>

#define MESSAGE_SIZE 50  // 消息缓冲区大小
#define MAX_CLIENTS 10  // 最大客户端数量

struct shared_memory {
  int read_ptr;  // 读指针
  int write_ptr;  // 写指针
  char messages[MAX_CLIENTS][MESSAGE_SIZE];  // 消息缓冲区
};

int shared_mem_id;  // 共享内存ID
struct shared_memory* shared_mem_ptr;  // 共享内存指针

int client_id;  // 客户端编号

void sighandler(int signum) {
  printf("[CLIENT %d] received signal %d\n", client_id, signum);

  // 更新读写指针
  shared_mem_ptr->read_ptr++; // 暂时不考虑溢出问题
  shared_mem_ptr->read_ptr %= MAX_CLIENTS;
  
  // 读取新消息
  char message[MESSAGE_SIZE];
  strncpy(message, shared_mem_ptr->messages[shared_mem_ptr->read_ptr], MESSAGE_SIZE);

  printf("[CLIENT %d] received new message: %s\n", client_id, message);
}

int main() {
  // 连接服务器
  int server_fd = connect(0, NULL, NULL);
  if (server_fd == -1) {
    perror("connect");
    exit(1);
  }

  // 获取客户端编号
  printf("[CLIENT] enter client ID (0-9): ");
  scanf("%d", &client_id);

  // 映射共享内存
  shared_mem_id = shmget(IPC_PRIVATE, sizeof(struct shared_memory), IPC_CREAT | 0644);
  if (shared_mem_id == -1) {
    perror("shmget");
    exit(1);
  }
  shared_mem_ptr = (struct shared_memory*) shmat(shared_mem_id, NULL, 0);
  if (!shared_mem_ptr) {
    perror("shmat");
    exit(1);
  }

  // 通知服务器新客户端加入
  char message[MESSAGE_SIZE];
  sprintf(message, "Client %d joined the chat room", client_id);
  write(server_fd, message, MESSAGE_SIZE);

  // 设置信号处理器
  struct sigaction act;
  act.sa_handler = sighandler;
  sigaction(SIGHUP, &act, NULL);

  // 等待服务器发送新消息的信号
  printf("[CLIENT %d] waiting for new messages...\n", client_id);
  while (1) {
    pause();
  }
}
参考资料