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

程序:利用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 1068

void 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 1068

void 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()
{
    //将连接标志置为0
    linkFlags=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

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

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值