进程间通信:fifo实现本地多人聊天程序

知识点

作用:

创建一个有名管道,解决无血缘关系的进程间通信。

shell脚本创建一个fifo示例
C函数
#include <sys/tpyes.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
  • 当只写打开fifo管道时,如果fifo读端没有打开,则写打开会阻塞。

  • fifo内核实现时可以支持双向通信(pipe单向通信,因为父子进程共享同一个file结构体)。

  • fifo可以一个读端,多个写端,也可以多个读端,一个写端。

通信示意图

程序分析

程序功能示意图
server进程:
  • mkfifo函数创建一条公共fifo,用open函数以O_RDONLY | O_NONBLOCK模式打开。循环用read函数读取客户端写给服务端的数据并进行处理。

  • 用fcntl函数设置STDIN_FILENO(接收键盘输入)为非阻塞(O_NONBLOCK),因为需要接收输入exit命令进行关闭服务器等等。循环用read函数接收键盘的输入。

  • 当客户端进行登录连接server时,用mkfifo函数为客户端创建一条私有fifo,用write函数写数据给客户端告诉客户端连接成功,每次通信完,需要关闭私有fifo的文件描述符。

client进程:
  • open函数以O_WRONLY模式打开公共fifo,open函数以O_RDONLY | O_NONBLOCK模式打开私有fifo,循环用read函数读取服务端写给客户端的数据并进行处理。

  • 用fcntl函数设置STDIN_FILENO(接收键盘输入)为非阻塞(O_NONBLOCK),因为需要从私有fifo读服务端写给客户端的数据,同时客户端也接收键盘的输入,进行聊天,同时也需要将屏幕输出等等。循环用read函数接收键盘的输入。

server_fifo.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#define SERVER_FIFO_FILE "server_fifo_file"
#define BUF_SIZE 1024
#define CHAT_HEAD "send "
#define CHAT_HEAD_LEN strlen(CHAT_HEAD)

#define MSG_BEGIN 0
#define MSG_C2S_LOGIN 1
#define MSG_S2C_CONNECT 2
#define MSG_C2S_CHAT 3
#define MSG_S2C_CHAT 4
#define MSG_C2S_OFFLINE 5
#define MSG_S2C_OFFLINE 6
#define MSG_END 7

enum USER_STATUS
{   
    ONLINE = 1,
    OFFLINE = 2
};

struct MSG
{
        int id;
        char src[32];
        char dst[32];
        char data[512];
};

//用户数据链表
struct USER_NODE
{
    struct USER_NODE *pre;
    struct USER_NODE *next;
    unsigned short status; //0:不在线 1:在线
    char name[32]; //用户名    
};

struct USER_LIST
{
    int count;
    struct USER_NODE *head;
    struct USER_NODE *tail;
} g_user_list;

void insert_user(const char *name);
struct USER_NODE* find_user(const char *name);
void print_user_list();
void update_user_status(const char *name, int status);

void sys_err(const char *str, int exitno)
{
    perror(str);
    exit(exitno);
}

void exit_chat();
void user_offline(struct USER_NODE *puser);

void c2s_login(struct MSG *pmsg);
void c2s_offline(struct MSG *pmsg);
void c2s_chat(struct MSG *pmsg);

int main(int argc, char *argv[])
{
    if (access(SERVER_FIFO_FILE, F_OK) < 0) //文件不存在
    {
        if(mkfifo(SERVER_FIFO_FILE, 0777) < 0) //创建一条公共命名管道
            sys_err("mkfifo", 1); 

        printf("%s mkfifo success\n", SERVER_FIFO_FILE);
    }

    //设置标准输入为非阻塞
    int flags = fcntl(STDIN_FILENO, F_GETFL);
    flags |= O_NONBLOCK;
    if (fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
        sys_err("fcntl", 1);

    int fd_r; //用于打开共享管道,读客户端发给服务端的数据
    int len = 0;
    struct MSG msg;
    char buf[BUF_SIZE];

    fd_r = open(SERVER_FIFO_FILE, O_RDONLY | O_NONBLOCK); //只读,用于接收客户端数据
    if (fd_r < 0)
        sys_err("open", 1);

    while(1)
    {
        //printf("loop begin\n");
        memset(&msg, 0, sizeof(struct MSG));
        memset(buf, 0, BUF_SIZE);

        //读共享管道数据
        len = read(fd_r, &msg, sizeof(struct MSG));
        if (len > 0)
        {    
            switch (msg.id)
            {
                case MSG_C2S_LOGIN:
                {
                    c2s_login(&msg);
                    break;
                }
                case MSG_C2S_CHAT:
                {
                    c2s_chat(&msg);
                    break;
                }
                case MSG_C2S_OFFLINE:
                {
                    c2s_offline(&msg);
                    break;
                }
                default:
                {
                    printf("not handle message id = %d\n", msg.id);
                    break;
                }
            }
        }
        else if (len == 0) //写端都关闭了
        {
            //printf("%s: write port close!\n", SERVER_FIFO_FILE);
            //sleep(1);
        }
        else
        {    
            if (errno != EAGAIN)
                sys_err("read", 1);
        }

        //读标准输入数据
        len = read(STDIN_FILENO, buf, BUF_SIZE); 
        if (len > 0)
        {
            if (strncmp(buf, "exit", 4) == 0)
                break;
        }
        else if (len == 0)
        {
        }
        else
        {
            if (errno != EAGAIN)
                sys_err("read", 1);
        }

        usleep(100000);
        //printf("loop end!\n");  
    }
    
    exit_chat();
    close(fd_r);
    unlink(SERVER_FIFO_FILE);

    return 0;
}

void c2s_login(struct MSG *pmsg)
{
    printf("c2s_login msg: id = %d, src = %s, dst = %s, data = %s\n", pmsg->id, pmsg->src, pmsg->dst, pmsg->data);

    if (access(pmsg->src, F_OK) < 0) //文件不存在
    {
        if (mkfifo(pmsg->src, 0777))    //为客户端创建一条私有命名管道
            sys_err("mkfifo", 1);
    }
    int fd_w = open(pmsg->src, O_WRONLY);
    if (fd_w < 0)
        sys_err("open", 1);

    struct USER_NODE *p = find_user(pmsg->src);
    if (p)
        update_user_status(pmsg->src, ONLINE);
    else
        insert_user(pmsg->src);

    struct MSG msg;
    msg.id = MSG_S2C_CONNECT;
    if (write(fd_w, &msg, sizeof(msg)) < 0)
        sys_err("write", 1);

    close(fd_w);        
}

void insert_user(const char *name)
{
    struct USER_NODE *p = find_user(name);
    if(p)
    {
        printf("insert fail, %s alread exist\n", name);
        return;
    }

    struct USER_NODE *user = (struct USER_NODE *)malloc(sizeof(struct USER_NODE));
    user->status = ONLINE;
    strcpy(user->name, name);

    if (g_user_list.count == 0)
    {
        user->pre = NULL;
        user->next = NULL;

        g_user_list.head = user;
        g_user_list.tail = user;
        g_user_list.count++;
    } 
    else
    {
        user->pre = g_user_list.tail;
        user->next = NULL;

        g_user_list.tail->next = user;
        g_user_list.tail = user;
        g_user_list.count++; 
    }
    printf("insert user success:%s\n", user->name);

    print_user_list();
}

struct USER_NODE* find_user(const char *name)
{
    struct USER_NODE *p = g_user_list.head;
    while(p)
    {
        if (strcmp(name, p->name) == 0)
            return p;

        p = p->next;        
    }    
    return NULL;
}

void print_user_list()
{
    struct USER_NODE *p = g_user_list.head;
    while(p)
    {
        printf("%s, status = %d\n", p->name, p->status);
        p = p->next;        
    }
}

void update_user_status(const char *name, int status)
{
    struct USER_NODE *p = find_user(name);
    if (!p)
    {
        printf("update fail:%s not exist", name);
        return;
    }

    p->status = status;    
}

void c2s_offline(struct MSG *pmsg)
{
    printf("c2s_offline msg: id = %d, src = %s, dst = %s, data = %s\n", pmsg->id, pmsg->src, pmsg->dst, pmsg->data);
    update_user_status(pmsg->src, OFFLINE);
}

void c2s_chat(struct MSG *pmsg)
{
    printf("c2s_chat msg: id = %d, src = %s, dst = %s, data = %s\n", pmsg->id, pmsg->src, pmsg->dst, pmsg->data);
    struct MSG msg;
    memset(&msg, 0, sizeof(struct MSG));
    msg.id = MSG_S2C_CHAT;

    struct USER_NODE *p = find_user(pmsg->dst);
    if (!p) //通知发送方,dst用户不存在
    {
        strcpy(msg.src, "not-exist");
        strcpy(msg.dst, pmsg->dst);
    }
    else if (p->status == OFFLINE) //通知发送方,dst用户不在线
    {
        strcpy(msg.src, "user-offline");
        strcpy(msg.dst, pmsg->dst);
    }
    else //将聊天信息转发给接收方
    {
        pmsg->id = MSG_S2C_CHAT;
    }
    

    if (pmsg->id != MSG_S2C_CHAT)
    {
        if (access(pmsg->src, F_OK) < 0) //文件不存在
        {
            if (mkfifo(pmsg->src, 0777) < 0)
                sys_err("mkfifo", 1);
        }

        int fd_w = open(pmsg->src, O_WRONLY);
        if (fd_w < 0)
            sys_err("open", 1);

        if (write(fd_w, &msg, sizeof(msg)) < 0)
            sys_err("write", 1);
            
        close(fd_w);
    }
    else //将聊天信息转发给接收方
    {
        if (access(pmsg->dst, F_OK) < 0) //文件不存在
        {
            if (mkfifo(pmsg->dst, 0777) < 0)
                sys_err("mkfifo", 1);
        }

        int fd_w = open(pmsg->dst, O_WRONLY);
        if (fd_w < 0)
            sys_err("open", 1);

        pmsg->id = MSG_S2C_CHAT;
        if (write(fd_w, pmsg, sizeof(*pmsg)) < 0)
            sys_err("write", 1);

        close(fd_w);
    }
}

void exit_chat()
{
    printf("开始退出聊天服务器,开始释放内存......\n");
    struct USER_NODE *p = g_user_list.head;
    struct USER_NODE *next = NULL;
    while(p)
    {
        if (p->status == ONLINE)
            user_offline(p);

        unlink(p->name);

        next = p->next;
        free(p);
        p = next;
    }

    printf("释放内存成功,退出聊天服务器成功......\n");
}

void user_offline(struct USER_NODE *puser)
{
    int fd_w = open(puser->name, O_WRONLY);
    if (fd_w < 0)
        sys_err("open", 1);

    struct MSG msg;
    msg.id = MSG_S2C_OFFLINE;
    if (write(fd_w, &msg, sizeof(msg)) < 0)
        sys_err("write", 1);
    
    close(fd_w);
}

client_fifo.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#define SERVER_FIFO_FILE "server_fifo_file"
#define BUF_SIZE 1024
#define CHAT_HEAD "send "
#define CHAT_HEAD_LEN strlen(CHAT_HEAD)

#define MSG_BEGIN 0
#define MSG_C2S_LOGIN 1
#define MSG_S2C_CONNECT 2
#define MSG_C2S_CHAT 3
#define MSG_S2C_CHAT 4
#define MSG_C2S_OFFLINE 5
#define MSG_S2C_OFFLINE 6
#define MSG_END 7

struct MSG
{
        int id;
        char src[32];
        char dst[32];
        char data[512];
};
 
void sys_err(const char *str, int exitno)
{
    perror(str);
    exit(exitno);
}

void s2c_connect(struct MSG *pmsg);
void s2c_chat(struct MSG *pmsg);
void s2c_offline(struct MSG *pmsg);

int main(int argc, char * argv[])
{
    if (argc < 2)
    {
        printf("%s player name\n", argv[0]);
        exit(1);
    }

    if (access(SERVER_FIFO_FILE, F_OK) < 0)
    {
        printf("server offline\n");
        exit(1);
    }     

    //设置标准输入为非阻塞
    int flags = fcntl(STDIN_FILENO, F_GETFL);
    flags |= O_NONBLOCK;
    if (fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
        sys_err("fcntl", 1);    

    int fd_w; //用于打开共享管道,写客户端发给服务端的数据
    int fd_r; //用于打开私有管道,读服务端发给客户端的数据 
    int len = 0;
    struct MSG msg;
    char buf[BUF_SIZE];

    fd_w = open(SERVER_FIFO_FILE, O_WRONLY);
    if (fd_w < 0)
        sys_err("open", 1);

    msg.id = MSG_C2S_LOGIN;
    strcpy(msg.src, argv[1]);
    len = write(fd_w, &msg, sizeof(msg));
    if (len < 0)
        sys_err("write", 1);
    sleep(1); //保证服务端收到以后建立私有管道

    fd_r = open(argv[1], O_RDONLY | O_NONBLOCK); //非阻塞读私有管道
    if (fd_r < 0)
        sys_err("open", 1);
    
    while(1)
    {
        //printf("loop begin\n");
        memset(&msg, 0, sizeof(struct MSG));        
        memset(buf, 0, BUF_SIZE);

        //读私有管道数据
        len = read(fd_r, &msg, sizeof(struct MSG));
        if (len > 0)
        {
            switch (msg.id)
            {
                case MSG_S2C_CONNECT:
                {
                    s2c_connect(&msg);
                    break;            
                }
                case MSG_S2C_CHAT:
                {
                    s2c_chat(&msg);
                    break;
                }
                case MSG_S2C_OFFLINE:
                {
                    s2c_offline(&msg);
                    break;
                }
                default:
                {
                    printf("not handle message id = %d\n", msg.id);
                    break;
                }
            }

            if (msg.id == MSG_S2C_OFFLINE) //服务器将用户主动踢下线
                break;
        }
        else if (len == 0)
        {
            //printf("%s: write port close!\n", argv[1]);
        }
        else
        {
            if (errno != EAGAIN)
                sys_err("read", 1);
        }
        
        //读标准输入数据
        len = read(STDIN_FILENO, buf, BUF_SIZE);
        if (len > 0)
        {
            memset(&msg, 0, sizeof(struct MSG));
            if (strncmp(buf, "exit", 4) == 0)
            {
                msg.id = MSG_C2S_OFFLINE;
                strcpy(msg.src, argv[1]);
                len = write(fd_w, &msg, sizeof(msg));
                if (len < 0)
                    sys_err("write", 1);
                
                //sleep(1); //保证服务端收到下线消息
                break;
            }
            else
            {
                char *p = strchr(buf, ':');
                int n = strspn(buf, CHAT_HEAD);
                if (p && n == CHAT_HEAD_LEN) //是发送聊天内容格式 send zhangsan: Hello
                {    
                    msg.id = MSG_C2S_CHAT;
                    strcpy(msg.src, argv[1]);
                    strncpy(msg.dst, buf+CHAT_HEAD_LEN, p-buf-CHAT_HEAD_LEN);
                    strcpy(msg.data, p+1); //去掉开头的:,所以+1
        
                    //printf("dst = %s\n", msg.dst);
                    //printf("data = %s\n", msg.data);
                    
                    len = write(fd_w, &msg, sizeof(msg));
                    if (len < 0)
                        sys_err("write", 1);
                }

            }
        }
        else if (len == 0)
        {
            printf("STDIN_FILENO\n");
        }
        else
        {
            if (errno != EAGAIN)
                sys_err("read", 1);
        }

        //printf("loop end\n");
        usleep(100000);
    }

    close(fd_w);
    close(fd_r);

    return 0;
}

void s2c_connect(struct MSG *pmsg)
{
    printf("connect success\n");
}

void s2c_chat(struct MSG *pmsg)
{
    //printf("s2c_chat:src = %s, dst = %s, data = %s\n", pmsg->src, pmsg->dst, pmsg->data);
    char buf[BUF_SIZE] = {0};
    if (strcmp(pmsg->src, "not-exist") == 0)
    {
        sprintf(buf, "%s 用户不存在\n", pmsg->dst);
    }
    else if (strcmp(pmsg->src, "user-offline") == 0)
    {
        sprintf(buf, "%s 用户不在线\n", pmsg->dst);
    }
    else
    {
        sprintf(buf, "recv %s: %s", pmsg->src, pmsg->data);
    }
    write(STDOUT_FILENO, buf, strlen(buf));     
}

void s2c_offline(struct MSG *pmsg)
{
}

运行效果截图

  • 启动服务器时

服务端

  • 启动客户端并以lisi用户登录时

客户端

服务端

  • 再启动多一个客户端并以jim用户登录,lisi跟jim聊天

客户端:lisi

服务端:将lisi发给jim的信息转发给jim

客户端:jim

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值