前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux 实现群聊功能

Linux 实现群聊功能

原创
作者头像
brzhang
修改2023-12-11 18:56:56
5155
修改2023-12-11 18:56:56
举报
文章被收录于专栏:玩转全栈玩转全栈

今天的主题是在 Linux 上实现一个群聊功能,支持群聊,指定人私聊,群主禁言,踢出群聊的功能,实际上要实现这个功能,如果你阅读过我前两天我写一篇 Linux原始系统api实现两个终端实时聊天 ,那么,在以上的基础上其实就是追加一下 两个功能即可,及群主禁言,和将谁踢出群聊的功能,因为群聊的基本功能我们实现了,而且私信的逻辑我们也实现了,ps,文本的代码在此

先回顾一下上篇文章的内容,我们实现的第一个版本,实际上就是一个大的群聊功能,包含服务端和客户端两部分,具体的交互如下:

  • 首先,服务端启动,等待客户端连接
  • 客户端 A,B,C …等 连接进来,我们支持了一个最大群聊人数,因为资源不可能是无限的。
  • A 向服务端发送一条消息
  • 服务端收到 A 的消息之后,将这条消息转发给到了所有的人,当然除了 A,这里的代码逻辑一撇如下,全部详细的代码就需要参考 Linux原始系统api实现两个终端实时聊天 这里了。

客户端连接到服务器的逻辑,并分配一个处理函数

代码语言:javascript
复制
// Accept clients
  while (1) {
    socklen_t clilen = sizeof(cli_addr);
    connfd = accept(listenfd, (struct sockaddr*)&cli_addr, &clilen);

    // Check if max clients is reached
    if ((cli_count + 1) == MAX_CLIENTS) {
      printf("Max clients reached. Rejected: ");
      print_client_addr(cli_addr);
      printf(":%d\\n", cli_addr.sin_port);
      close(connfd);
      continue;
    }

    // Client settings
    client_t *cli = (client_t *)malloc(sizeof(client_t));
    cli->address = cli_addr;
    cli->sockfd = connfd;
    cli->uid = uid++;

    // Add client to the queue and fork thread
    queue_add(cli);
    pthread_create(&tid, NULL, &handle_client, (void*)cli);

    // Reduce CPU usage
    sleep(1);
  }

实现群聊中将消息发送给到其他人的逻辑。

代码语言:javascript
复制
void send_message(char *s, int uid) {
    pthread_mutex_lock(&clients_mutex);

    for (int i = 0; i < MAX_CLIENTS; ++i) {
        if (clients[i]) {
            if (clients[i]->uid != uid) {
                if (write(clients[i]->sockfd, s, strlen(s)) < 0) {
                    perror("ERROR: write to descriptor failed");
                    break;
                }
            }
        }
    }

    pthread_mutex_unlock(&clients_mutex);
}

私信的逻辑

我们是一个中心化的聊天版本,也就是 A 客户端发送的消息先会到服务器,服务器在进行转发,群聊就是将 A 发送的消息转发给到其他连接到这个服务器的其他所有人,所谓的发起一个私信,即这个发送的消息是不能被转发给到所有其他人,那你就需要和服务器约定消息格式了:

我们通过这样的方式来实现私密消息功能:

  1. 消息格式:我们约定私密消息的格式为 /msg <recipient_name> <message>
  2. 服务器端方面:当服务端它检测到一条消息以 /msg 开头时,它会查找指定的接收者,并只将消息发送给那个特定的客户端。

这是我们服务端处理私信逻辑的部分

代码语言:javascript
复制
// msg 是接收到的完整消息,sender 是发送者的名字。

void handle_private_message(char *msg, char *sender) {
    // 解析消息格式,提取接收者的名字和实际的私密消息
    char recipient[32];
    char private_msg[LENGTH];
    if (sscanf(msg, "/msg %s %[^\\n]", recipient, private_msg) == 2) {
        // 查找接收者的客户端结构体
        Client *client = find_client_by_name(recipient);
        if (client) {
            // 发送私密消息给接收者
            char buffer[LENGTH + 32];
            sprintf(buffer, "[Private]%s: %s\\n", sender, private_msg);
            send(client->sockfd, buffer, strlen(buffer), 0);
        } else {
            // 接收者不存在,发送错误消息给发送者
            char buffer[LENGTH];
            sprintf(buffer, "User %s does not exist.\\n", recipient);
            send(sender_sockfd, buffer, strlen(buffer), 0);
        }
    }
}

私信的效果是:

A 发送一条给到 B 的私信,只有 B 可以收到,C 是收不到的

只有 B 收到的截图

C 是收不到的

继续实现禁言某人和踢出用户的功能

要实现禁言的功能,我们的思考是,如何能够让用户发送的消息不会被其他群聊的人看到,所以,最为直观的实现逻辑就是服务端丢弃被禁言的用户发送过来的消息,因此,我们需要在 client_c 结构中标记下哪个 client 被 mute 了,然后修改一下 send_message 逻辑即可,当发现这个 client 是被 mute 了的,就不转发他的消息了,禁言和踢出用户的整体逻辑图如下:

我们先实现对用户禁言的 部分,解除禁言就不贴了,将to_be_unmute->mute = 0; 就 ok 了

代码语言:javascript
复制
else if(strncmp(s, "/mute ", 8) == 0 && uid == 0){ //uid 为 0 的是群主
        char unmute_user[32];
        char *message;
        char buffer[BUFFER_SIZE + 32];
        // 解析消息
        message = s + 8; // 跳过 "/unmute "
        sscanf(message, "%s", unmute_user);
        //unmute client
        client_t * to_be_unmute = find_client_by_mute_name(unmute_user);
        if (to_be_unmute) {
            to_be_unmute->mute =  1;
        }
        return;
    }

// 省略...

// Send message to all clients ,if client is not muted
        client_t * client = find_client_by_uid(uid);
        if(client->mute == 1){
            // 告知 客户端他已经被屏蔽了
            char buffer[BUFFER_SIZE];
            sprintf(buffer, "Your message has been blocked.\\\\n");
            write(client->sockfd, buffer, strlen(buffer));
            pthread_mutex_unlock(&clients_mutex);
            return;
        }

我们看看屏蔽一个用户的效果, A作为第一个用户,加入群聊,是群主,后面 C 加入了,A 发送了屏蔽指令将其屏蔽

然后看看 C 发送消息,发现他被屏蔽了

B 不会看到他发送的消息,ps 这里我们没有屏蔽私信,所以 C 是可以给 B 发送私信的。

2.实现踢出的逻辑

这里的实现方式和实现屏蔽略微不同,而且还稍显简单,直接干掉 server 和 client 的连接即可,实现的方式如下:

代码语言:javascript
复制
else if (strncmp(s, "/kick ", 6) == 0 && uid == 0) {
        char kick_user[32];
        char *message;
        char buffer[BUFFER_SIZE + 32];
        // 解析消息
        message = s + 6; // 跳过 "/kick "
        sscanf(message, "%s", kick_user);
        //kick client
        client_t * to_be_kick = find_client_by_mute_name(kick_user);
        if (to_be_kick) {
            //断开与该客户端的 socket 连接,哈哈,比较暴力哈,踢出群聊
            close(to_be_kick->sockfd);
            remove_client(to_be_kick->uid); // 从客户端列表中移除
        }
    }

我们来验证下效果,A,B,C 先后加入群聊,A 是群主,A 踢掉 C,然后在发送一条群消息

我们看到 C 这里收不到消息,B 是可以收到的,这说明 C 已经被踢掉了。

总结

今天的内容,基于上一版的群聊+简单的私信的版本的基础上只另外实现了 屏蔽用户 和 踢下线的功能,功能都非常简单,大家不妨思考一下,基于这个版本的的基础上,我们还可以做哪些功能呢?我能想到的:

  • 客户端可以请求服务端返回目前在线的用户列表,即一个简单的关系链
  • 这是一个命令行应用,那么可否给用户提供一个界面的方式呢?提升用户体验?
  • 则是一个中心化的服务,你能够想办法将它变为一个去中心化的吗?即任何一个客户端都可以是一个服务端。这样避免服务端没启动的时候,咱们这玩意玩不了。
  • 你能为这个项目写一个 docker 描述文件吗?

虽然这是一个极其简单的 demo,但是,如果深入思考,能做的优化是在是太多了,优化本身就是一个不断追求的过程。

我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 客户端连接到服务器的逻辑,并分配一个处理函数
  • 实现群聊中将消息发送给到其他人的逻辑。
  • 私信的逻辑
  • 继续实现禁言某人和踢出用户的功能
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档