Linux 命名管道 聊天室

欢迎大家点击链接,查看本项目的 ✌说明文档

一、功能说明

实现的功能

  1. 客户端之间的群聊功能已经实现
  2. 实现了使用用户名标识的私聊功能
  3. 某个客户端退出后,其余客户端依然可以正常通信
  4. 优化了客户端界面
  5. 录制了测试视频并投稿至B站

具体操作内容概述
本程序代码继承自上一次嵌入式作业,主要做了以下改动

  • 客户端
  1. 将握手函数拆分为数据发送函数和数据接受函数,提高函数复用率。
  2. 使用父子进程的方式实现了群聊功能,实现了避免产生孤儿进程的功能。
  3. 编写数据过滤函数,将私信数据的目标PID和私信消息过滤。
  • 服务器
  1. 服务器将现存客户端数量、客户端的PID以及用户名都做记录
  2. 编写服务器的数据发送函数,能够实现不将数据发给发来数据的客户端,此部分代码已被注释(觉得暂时没用)。实现了私发功能。

二、Bug集中营

2.1 关于群聊的问题

  • 问题描述
    刚刚遇到一个问题: 当第二个客户端连接进来之后,服务器就会依次向所有客户端发送相关信息。但是,此时第一个客户端正处在发送数据的情况下。也就是说,此时如果客户端1不能发送数据给服务器,那么此时服务器发给客户端1的信息就会被阻塞。这就导致其他所有客户端都不能接收到服务器的此条信息了。

  • 问题原因
    该问题最根本的原因是,客户端的读写是顺序执行的,他只能写完数据之后才能有机会读数据。这就导致他写(公有管道)数据的时候,读(私有管道)被阻塞了。

  • 解决方案
    如果要解决该问题,我觉得 必须 有必要采用父子进程的方式。这样就会解决读写顺序的问题。

PS: 本以为该问题很棘手,需要大量修改代码 😵
没想到,加上父子进程后,轻松解决 😜~~~

  • 需要注意的问题
    在父子进程的程序中,需要注意避免产生孤儿进程和僵尸进程。本程序中,父进程用来向服务器写入数据,子进程用来从服务器读出数据。如果客户端想要退出程序,父进程首先向服务器发送CLIENT_QUIT消息,然后箱子进程发送SIGSTOP信号,终止子进程执行。这样可以避免子进程变为孤儿进程或僵尸进程。

下面附一张子进程变成孤儿进程的截图:
孤儿进程

2.2 关于私聊的问题

  • 如何实现两个客户端的私聊?
    若要实现客户端之间的私聊,可以在客户端发送的数据前加入报头,比如(to: [Client_PID] [Client_Message])。这样,就有两种处理方式:

    • 方式一、在客户端判断是否为私聊
      客户端自身判断一下前几个字符是否为固定报头。如果是的话,就截取目标PID和待发送的消息,同时用自己结构体中的成员标志记录下目标PID。服务器收到消息后,判断结构体的成员标记。如果是私聊,就单独发送;否则就向所有客户端循环广播发送数据。
    • 方式二、在服务器判断是否为私聊
      客户端加上包头之后,直接将所有数据发送至服务器。服务器接收到消息后,判断报头是否为私聊报头,进而作出相应处理。
      为了减轻服务器的数据处理负担,我选择在客户端处理数据。
  • 关键问题
    问题的关键在于如何正确地切分数据。看了看C语言的正则表达式。。。我还是自己写函数吧。这里的问题还是很多的,主要是一些细节问题。比如,进程的PID最多只有5位数,如果用户输入了错误的PID(长度超过5位数),如何进行位数限制;还有,假如用户没有严格按照规定格式输入,而是输入了一些无效的空格,怎样提高数据分割的容错性。等等问题。我写了个函数,实现了以下功能:

    • 能够按照规定格式正确切分客户端PID和客户端的有效消息
    • 能够忽略客户端所有无效的空格字符
    • 能够限制目标PID在5位数以内
      客户端函数的目的,是能将私信目标PID和有效数据分隔开,只要能实现数据分割就可以了。至于判断目标PID是否有效,可以在服务器端实现。
  • 如何判断私信目标PID是否有效?
    现在的想法是,服务器读取私有管道文件,如果私有管道不存在,则拒绝通信。其实目前已经实现了该效果。如果服务器循环遍历之后找不到对应文件名的客户端的私有管道文件,那就什么都不做。

2.3 关于用户名的问题

昨天已经实现了群聊和私聊的功能,但是在私聊时需要输入对方客户端的PID识别号,很不方便。目前的想到的解决方法是:在每一个客户端启动时,首先要求用户输入它的用户名,私聊时以用户名作为客户端的标识。

  • 问题关键
    如果使用用户名作为标识的话,需要在客户端发送的结构体数据中,添加用户名的成员变量。在私聊时,将数据分割方式由分割PID换成分割用户名。这样其实会简单一些,因为不用再去判断PID和消息内容了。
    调了一会,现在功能已经实现了。这部分的功能实现,因为与之前的思路(用客户端PID作为标识)有不同的地方,所以修改起来还是比较麻烦的,修改的代码比较多。

  • 解决方案

  1. 客户端启动之后,就要求用户输入用户名。该用户名被保存在客户端的结构体成员变量中,发给服务器。
  2. 在客户端进行数据的过滤,如果是私发信息,就将私发对象的用户名和要私发的信息拆分开来,分别存储到结构体成员变量中。如果不是私发信息,就在相应的结构体变量中添加标识。
  3. 服务器收到客户端的结构体数据之后,将该客户端的PID和用户名,分别保存在两个数组中。
  4. 服务器通过检测结构体中的target_name,来判断是否该数据需要私发。如果需要私发,就执行私发代码;否则执行循环发送代码。
  5. 私发的情况下,服务器首先需要在用户名数组中,循环查找一下是否存在该用户。如果存在该用户,那么可以向对方发送消息,同时获取该用户在用户结构体中的座标(它是第几个用户)。根据这个目标用户的座标,就可以找到与它对应的PID,进而可以打开私有管道,只对其私发信息。
  6. 同样,通过在用户PID数组中循环查找发送消息的用户位置,可以找到与其对应的用户名。进而可以知道,是那个用户发出的消息。

PS: 我自认为在解决该问题中,比较巧妙地用到了 用户名数组和用户PID数组的下标一致性 这样的特点。

2.4 关于客户端界面的问题

2.4.1 时间显示问题

本来是想用QT做一个界面,但估计我短时间做不出来。。。
那就先把客户端的终端界面搞一搞。模仿QQ的聊天室界面,在用户输入消息之后,需要在每个消息之前加上信息发送的时间。

  • 问题分析

给客户端的每条消息加上时间,也有两种解决方式。

  1. 客户端自己将时间信息加到消息结构体中,用结构体成员保存时间信息。
  2. 由服务器加上时间前缀。服务器回复每条消息前,都在信息前面加上当前时间作为消息前缀。
  • 解决方式
    经过我的测试,发现QQ是由服务器统一显示的时间,大概是为了统一信息接收时间。但是我想要减轻一下服务器的负担,所以就由客户端自身写入当前时间。
    时间的显示比较容易,直接调用库函数就可以了。

2.4.2 握手消息问题

  • 问题分析
    每个客户端在启动之后,首先就会向服务器发送一个握手消息,以便服务器为其创建私有管道。但是现在每次的握手消息会被发送至所有客户端,只显示握手消息的内容的话不太友好。所以需要改变一下握手信息的显示方式。

  • 解决方式
    处理方式也很简单,就是在每次服务器群发数据之前,都事先判断一下当前的客户端是不是新来的客户端。如果是新的客户端,就改变消息发送的内容。

2.5 关于客户端退出的问题

2.5.1 客户端退出后,服务器如何向其他用户通知

  • 问题描述

现在的程序逻辑是这样的:当某个客户端要退出时,需要向服务器发送一个CLIENT_QUIT信息。服务器收到该信息后,切断该客户端的私有管道,删除私有管道文件。然后客户端数量的标识Client_Number减一。就完事了。
如果是现在这样处理的话,当某个客户端退出后,其他用户是不知道该客户端的退出的。这就不太好。。。

  • 解决方案

我的解决方案是:当某个客户端退出后,服务器同样做之前的动作(切断该客户端的私有管道、删除私有管道文件、客户端数量减一),同时,服务器也要向所有客户端广播消息,告诉其他用户,该客户端已退出。这里的关键问题就是如何判断客户端退出,我是设了一个标识位Quit_Flag,用标识位做判断,控制服务器广播的内容。具体内容可以看一下Server_Send_Message()函数。

2.5.2 如何保证某个客户端退出后,其他客户端信息接收的有效性

  • 问题描述

在解决第一个问题后,一个隐藏的问题就被暴露出来了。由于程序中对客户端信息的存储,是顺序排放在数组中的,而当某个客户端退出后,该位置的信息没有被删除,客户端数量却减了一。这就导致,循环发送信息时,最后加进来的客户端,一定收不到信息。简言之,某个客户端退出后,后面客户端对广播消息的接受会有异常。

  • 解决方案

之所以有这样的问题,根本原因是,服务器没有把已退出客户端的信息删除。解决办法也很简单,当某个客户端退出后,服务器把Client_PID_Box数组以及Client_Name_Box数组中的内容重新排序。实际上是,把该客户端后面的所有客户端信息,都往前移动一位。我写了个简单的Delete_Client_Data函数,实现了该功能。

三、效果展示

目前已录制了两个视频,分别是Linux_命名管道_聊天室_测试视频Linux_命名管道_聊天室_测试视频_V2.0

在这里插入图片描述

四、程序代码

源代码遵循 GNU General Public License v3.0 开源许可协议

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章