程序:利用FIFO实现本地聊天室

程序功能:实现本地进程间的相互通信

程序分析:通过FIFO管道可以实现本地(本机)上无血缘关系(进程不是fork()出来的)的进程间的通讯,而FIFO管道支持“一写多读”,“多写一读”。我们可以利用这一特点建立本地聊天室的模型。

服务器提供一条公共的管道,各个客户端通过这条公共管道发送消息给服务器,服务器接收到消息后,对消息包进行解析后交给相应的函数处理。如果是一个客户端发给另一个客户端的消息,就将消息通过私有管道转发下去,交给对应的客户端。

程序种使用的知识点:

(1)临时文件(unlink):因为通信时需要给每个客户端都创建一条私有的通道,如果使用普通的方式创建文件,就会产生大量的管道文件。不过如果使用数据库,就可以创建普通的文件,因为管道时根据客户端的客户名创建的,如果每次输入的名字的不同就产生不同的文件,使用数据库,可以通过读入客户机的文件名来解决这个问题。

(2)修该文件属性(fcntl):无论是客户端还是服务器都要求在可以发送和接收消息,这就要求文件时非阻塞的。使得文件获得非阻塞的属性的方法有两种:(1)在文件打开时将文件将文件修改为非阻塞的(私有管道)。(2)在文件打开后通过fcntl函数将文件的属性修改为非阻塞(STDIN_FILENO(会在程序运行时,默认打开))。

(3)字符串处理:在客户端相互发送消息时,需要输入接收方的客户机名称,规定格式为[接收方姓名:消息内容]。客户会输入一串字符串,需要进程处理,将字符串以第一个“:”为分割符,分割为两部分。如果没有按格式输入则会将消息群发(发给客户端列表上的每一个客户端)。

(4)深拷贝:在给字符串进程赋值一定要注意,使用strcpy函数,而不是简单的使用“=”。

(5)范型指针:将消息结构体转换为“范型指针”(void*),然后是使用char*接收(也可以直接使用viod*接收)。

程序中存在的问题:

(1)在客户端的管理上,使用了数组。数组虽然使用比较简单,但后期会有很多的麻烦,如空间浪费,客户端退出的处理不够灵活。

(2)在客户端登录时,没有对数据进程越界判断。

(3)没有对客户端的输入的客户名进行检查(会造成消息混乱)。

(4)数据包使用了定长包,处理上比较简单,但造成了浪费。

(5)客户端没有处理服务器退出的机制(虽然函数会自动关闭,但显得代码不是很完善)。

运行环境:CentOS7.0+Gcc

注意事项:公共管道使用mkfifo在控制台下直接建立的,然后使用了宏定义和绝对路径。

#define SERVER_FIFO "/home/kk/code/c++/localChatRoom/SERVER_FIFO"

默认私有管道的名称为客户机名称,创建在当前目录下,零食文件,使用后会被自动删除。

客户端和服务器的退出命令都为:quit-->()

客户端退出时,会关闭私用管道和公共管道(它自己的那一条的写端)

服务器退出时,会给每一个在线的客户端的发送消息,然后关闭私有的管道的写端和公共管道的读端。

在输入时不能输入空格,因为scanf函数遇空格会终止,会将消息从空格处截断,后面的消息会变成群发。可以利用sacnf的格式控制来解决。也可以使用gets_s函数(需要将文件改为Cpp),不推荐使用gets函数(没有错误检测)。

代码:

//localServer.h
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>#define SERVER_FIFO "/home/kk/code/c++/localChatRoom/SERVER_FIFO"struct client
{char clientName[20];//客户端名字int fifoDis;//私有管道的描述符
};typedef struct client CL;
//用来记录客户机的数量
int clientlen=0;
//利用数组将存储客户队列(不方便,而且会浪费),可以改造为链表(最好)。
CL clientDueue[100];struct messagePacket
{int messageNo;//消息编号char senderName[20];//消息发送方char receiverName[20];//消息接收方char data[1024];//数据采用不定长消息
};typedef struct messagePacket MSP;//公共管道
int serFifo;
//服务器启动标志
int startFlags=0;//初始化,负责初始化服务器。
void initServer();
//负责接收客户端发送的包
void receiverPacket();
//负责将客户端发送的包解析
void parsingPacket(MSP *msp);
//负责客户端登陆,将客户端插入客户队列中,并创建私有管道
void clientLogin(char* loginName);
//负责将消息发送到对应的接受方
void messageSend(MSP *pMsp);
//负责客户端的退出,将客户端从客户队列中删除,并删除创建的管道
void clientQuit(char* quitName);
//负责关闭服务器,关闭打开的管道和删除客户机列表
void closeServer();
//负责处理输入的数据
void messageHanle(char* pMes);
//localServer.c
#include "localServer.h"
#define BUFSIZE 1068void initServer()
{//将STDIN_FILENO修改为非阻塞int serFlags=fcntl(STDIN_FILENO,F_GETFL);serFlags|=O_NONBLOCK;fcntl(STDIN_FILENO,F_SETFL,serFlags);//以非阻塞只读的方式打开管道serFifo=open(SERVER_FIFO,O_RDONLY|O_NONBLOCK);if(serFifo<0){perror("SERVER OPEN:");exit(1);}printf("服务器已启动,正在监听...\n");startFlags=1-startFlags;
}void receiverPacket()
{char buf[BUFSIZE];MSP *msp;int len=read(serFifo,buf,sizeof(buf));if(len>0){msp=(MSP*)buf;parsingPacket(msp);}
}void parsingPacket(MSP *msp)
{//根据相应的功能号,调用相应的函数。switch(msp->messageNo){case 0:clientLogin(msp->senderName);break;case 1:messageSend(msp);break;case 2:clientQuit(msp->senderName);break;}
}void clientLogin(char* loginName)
{//不能直接赋值,会造成浅拷贝strcpy(clientDueue[clientlen].clientName,loginName);char path[23]="./";strcat(path,loginName);//确保创建的文件的权限为分配权限umask(0);//创建管道mkfifo(path,0777);//将管道的文件描述符存入数组中clientDueue[clientlen].fifoDis=open(path,O_WRONLY);char buf[]="您和服务器的连接已经成功建立,可以开始通讯了\n";write(clientDueue[clientlen].fifoDis,buf,sizeof(buf));//这里应该将管道创建为临时的,如果是使用数据库,可以创建为永久的unlink(path);//没有对cientlen进行限制++clientlen;
}void clientQuit(char* quitName)
{//最好是利用链表管理登录的客户机int i=0;for(i=0;i<clientlen;i++){if(strcmp(quitName,clientDueue[i].clientName)==0){//关闭对应的私有通过close(clientDueue[i].fifoDis);clientDueue[i].fifoDis=-1;clientDueue[i].clientName[0]='\0';break;}}printf("%s已退出\n",quitName);
}void messageSend(MSP *pMes)
{int i=0;char* buf=(void*)pMes;if(strlen(pMes->receiverName)!=0){//单发for(i=0;i<clientlen;++i){if(strcmp(pMes->receiverName,clientDueue[i].clientName)==0){write(clientDueue[i].fifoDis,buf,BUFSIZE);break;}}}else{//群发for(i=0;i<clientlen;++i){write(clientDueue[i].fifoDis,buf,BUFSIZE);}}
}void messageHanle(char* pMes)
{if(strcmp(pMes,"quit-->()")==0){closeServer();}//可以继续增加一些命令(显示有几个客户端,客户端的管道描述符等)
}
void closeServer()
{char buf[]="服务器维护中,请稍后登录。";int i=0;for(i=0;i<clientlen;++i){if(clientDueue[i].fifoDis!=-1){write(clientDueue[i].fifoDis,buf,strlen(buf));close(clientDueue[i].fifoDis);}}close(serFifo);startFlags=1-startFlags;printf("以关闭所有管道,服务器安全退出");
}
int main()
{initServer();char mes[1024];while(startFlags){receiverPacket();if(scanf("%s",mes)!=EOF){messageHanle(mes);}}return 0;
}
//localClient.h
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define SERVER_FIFO "/home/kk/code/c++/localChatRoom/SERVER_FIFO"int linkFlags=0;//连接标志
int serFifo;//公共管道文件描述符
int cliFifo;//客户端私有端道文件描述符
char clientName[20];//客户端名称struct messagePacket
{int messageNo;//消息编号char senderName[20];//消息发送方char receiverName[20];//消息接收方char data[1024];//数据采用定长消息
};typedef struct messagePacket MSP;//初始化客户大端
void initClient();
//登陆服务器
void loginServer();
//处理用户输入的数据
void messageHanle(char* pMes);
//向服务器发送消息
void sendSerMes(int mesNO);
//向其他用户发送消息
void sendMessage(char* receiverName,char* data);
//接收消息
void receiverMes();
//关闭客户端
void closeClient();
//localClient.c
#include "localClient.h"
#define BUFSIZE 1068void initClient()
{loginServer();//将连接标志置为1.linkFlags=1-linkFlags;//将STDIN文件属性修改为非阻塞int flags=fcntl(STDIN_FILENO,F_GETFL);flags |= O_NONBLOCK;fcntl(STDIN_FILENO,F_SETFL,flags);
}void loginServer()
{printf("请输入客户端名称(不超过20个字符):\n");//write(STDIN_FILENO,clientName,20);scanf("%s",clientName);serFifo=open(SERVER_FIFO,O_WRONLY|O_NONBLOCK);if(serFifo<0){perror("open server fifo");exit(1);}sendSerMes(0);char path[23]="./";strcat(path,clientName);//测试管道是否创建成功while(access(path,F_OK)!=0);cliFifo=open(path,O_RDONLY|O_NONBLOCK);if(cliFifo<0){perror("open client fifo");}printf("私有管道创建成功\n");
}void sendSerMes(int mesNO)
{MSP msp;char *buf;msp.messageNo=mesNO;strcpy(msp.senderName,clientName);buf=(void*)&msp;write(serFifo,buf,sizeof(msp));
}void messageHanle(char* pMes)
{//将“quit-->()”设置为退出消息if(strcmp(pMes,"quit-->()")==0){sendSerMes(2);closeClient();return;}//发送数据格式为:接受者姓名:消息内容//如果数据不符合规范,则将消息转为群发。int i=0;int j=0;char receiverName[20];char data[1024];while(pMes[i]!='\0'&&pMes[i]!=':'){receiverName[i]=pMes[i];++i;}receiverName[i]='\0';if(pMes[i]==':'){//将:跳过++i;}else{i=0;receiverName[0]='\0';}while(pMes[i]!='\0'){data[j++]=pMes[i++];}data[j]='\0';sendMessage(receiverName,data);
}void sendMessage(char* receiverName,char* data)
{MSP msp;char *buf;msp.messageNo=1;strcpy(msp.senderName,clientName);strcpy(msp.receiverName,receiverName);strcpy(msp.data,data);buf=(void*)&msp;write(serFifo,buf,sizeof(msp));
}void receiverMes()
{char buf[BUFSIZE];int len=read(cliFifo,buf,sizeof(MSP));MSP *pMes=NULL;pMes=(void*)buf; if(len>0&&pMes->messageNo==1){printf("%s:%s\n",pMes->senderName,pMes->data);}else if(len>0){printf("系统提示:%s",buf);}
}void closeClient()
{//将连接标志置为0linkFlags=1-linkFlags;//关闭私有管道close(cliFifo);//关闭公共管道close(serFifo);printf("以关闭所以管道,客户端安全退出\n");
}int main()
{initClient();char mesBuf[1024];while(linkFlags){//scanf()默认遇空格终止scanf("%49[^\n]",mesBuf)!=EOF//int len=write(STDIN_FILENO,mesBuf,BUFSIZE);if(scanf("%s",mesBuf)!=EOF){messageHanle(mesBuf);} receiverMes();}return 0;
}

结果展示:

图1、服务器端

图2、客户端1

图3、客户端2

图4、客户端3

如果发现问题,请留言,我会查证后修改,万分感谢。

Linux小练习(2)----利用FIFO实现本地聊天室(C/S模式)相关推荐

  1. 云信小课堂|搭建应用级别在线聊天室,7步就够了!

    Vol. 6 从2000年至今,聊天室一直活跃在人们的各种生活场景中,目前广泛运用于超级小班课.互动大班课.连麦开黑.主播 PK 等场景,还具备文本.表情.点赞.撒花等互动方式,架起沟通桥梁的同时,玩 ...

  2. spring boot练习--利用websocket实现QQ聊天室

    本篇介绍websocket实现QQ聊天室的后端实现,前端的实现看链接说明 链接说明 1.使用了spring boot 框架,有关spring boot 入门请戳此链接使用Intellij IDEA开发 ...

  3. js+html+css实现本地聊天室

    欢迎访问我的个人博客:http://mrzyf.club. 代码完成效果: 话不多说,直接上代码-- css代码: <style type="text/css">.ta ...

  4. 简单socket 聊天室 C/S模式 小例子

    一个简单的使用socket 的简单的 S/C 模式的聊天程序: 周六重新拿以前没弄好的代码改改,弄的:(客户端的程序就不想改了,那时写的太难看了代码) 下面就直接上 运行的截图,图形的东东弄的很粗糙 ...

  5. php 利用redis写一个聊天室,Redis实现多人多聊天室功能

    本文为大家分享了Redis支持多人多聊天室功能的设计代码,供大家参考,具体内容如下 设计原理 左边的一个数据域,代表两个聊天室,聊天室id分别是827,729 在聊天室827里,有2个人,分别是jas ...

  6. php 利用redis写一个聊天室,使用Redis完成聊天室功能

    Redis提供了Pub/Sub(发布/订阅)模式的消息机制.发布者向指定频道发布消息,订阅了该频道的订阅者就可以获取消息.通过该机制,我们可以完成聊天室.公告牌等功能. 首先,来介绍下关于pub/su ...

  7. Linux C多人网络聊天室

    经过好几天的日夜奋斗,总算把这个聊天室给做出来了,虽然说不上多好,但也是这几天从早到晚劳动的成功,所以就写这篇博文来记录一下啦.别的不敢说,确保能用就是了,完整代码在最后哦~ 当然啦,如果有幸被转发, ...

  8. linux点对点聊天室的实现与设计心得,基于Socket接口的Linux与Windows网络聊天室设计与实现...

    陈洁 孟晓景 摘要:为了实现Linux与Windows跨平台通信,及时共享信息,构建了一个适用于跨平台的网络聊天室通信程序.先搭建跨平台通信环境,然后使用Socket套接字网络编程接口实现通信.整个系 ...

  9. 网络聊天室(linux,java,Android)

    如果追忆会荡起涟漪,那么今天的秋红落叶和晴空万里都归你 艾恩凝 个人博客 https://aeneag.xyz/ 前几天在他人那里看到了网络聊天室的文章,想起了自己几年前也认认真真写过相关编程,实现了 ...

  10. 怎么用linux设计一个小程序,“Linux”小程序发布一个月后,我们发现了什么

    原标题:"Linux"小程序发布一个月后,我们发现了什么 这一个月来,这个小程序得到了八千多人的使用,一百多位贡献者实际参与了翻译贡献,其中贡献最高的"Datura st ...

最新文章

  1. EfficientNetV2:更小,更快,更好的EfficientNet
  2. Mxnet TensorRT
  3. 145.单工、半单工、双工
  4. android studio scala插件,Scala 语言开发Andorid ,开发环境的搭建(一)
  5. nutzwk oracle,NutzWk插件使用
  6. 一个Lex/Yacc完整的示例(可使用C++)
  7. F Christmas Game
  8. Android零基础入门第26节:layout_gravity和gravity大不同
  9. 工程量计算专用工具-支持灌注桩、搅拌桩、格构柱
  10. Excel批量删除空白行
  11. Java学习笔记(五):Complex类的设计及加减乘除运算的实现
  12. 机器学习 k-近邻算法
  13. 欧几里德算法(Euclidean algorithm)
  14. 利用反向代理服务器,加快国内对国外主机的访问
  15. 微信软文怎么写比较好?
  16. 当你压力大到快崩溃时,不要跟任何人说,也不要觉得委屈
  17. 用Java根据π/4=1-1/3+1/5-1/7...计算pi的值
  18. 软件是如何驱动硬件的?
  19. 文本压缩算法的对比和选择
  20. determined(determined是什么意思英语)

热门文章

  1. MobileNet-SSD网络解析
  2. 思维导图——史上最详细的计算机基础进制转换讲解
  3. php 多元数组,php数组_php多元数组
  4. 使用js实现网页录音并上传服务器
  5. 取之盈--别人轻松月薪过万,都是怎样高效学习的?
  6. python是一种什么类型的植物_植被类型预测
  7. 学校做计算机教室锐捷,锐捷网络云课堂:让学生爱上每一节课
  8. 怎样将手机屏幕投射到电脑
  9. python turtle菜鸟教程_【读书】Django教程(菜鸟教程)
  10. Sqlmap命令大全