C++ actual combat - imitation QQ project terminal version communication

Let's analyze first:

        The client may have multiple logins at the same time. The communication is actually an exchange of information between two clients, and the server only acts as an intermediary. (Just like when we communicate with QQ)

Next, we need to consider how the data is transmitted between the client and the server. Of course, network programming is not involved here. Simply practice system programming. There are many ways of IPC communication, let's choose a relatively simple way to install it. Use famous pipes

Before communication, let's plan how to organize data or how to organize messages [sender, receiver, data itself, protocol]

The protocol here is not the protocol in the network, but simply for the distinction: login, communication, offline, exit and other states [states that may appear on the client]

We package for easy transmission

qq_ipc.h

#ifndef QQ_IPC_H
#define QQ_IPC_H

/*
    protocal:
        1   登录
        2   数据传输
        3   不在线
        4   退出登录
*/

struct DATA_INFO{

    int  protocal;       //协议
    char srcname[20];    //发送方
    char destname[20];   //接收方
    char data[100];      //发送的数据
};

#endif

We also need to consider a problem, there will be multiple clients online at the same time. These clients all need to communicate with the server, so how does the server manage these clients?

The client will be in the status of online and exit, and relatively speaking, it is a very frequent operation, so it is convenient to insert and delete operations according to the linked list, so we use the linked list to manage online users

mylink.h

#ifndef _MYLINK_H_
#define _MYLINK_H_

typedef struct node *mylink;
struct node{

    char item[20];    //客户端的名字
    int fifo_fd;      //该客户端使用的私有管道
    mylink next;    

};

//初始化链表
void mylink_init(mylink *head);
//创建节点
mylink make_node(char *name,int fd);
//插入
void mylink_insert(mylink *head,mylink p);
//查找
mylink mylink_search(mylink *head,const char *keyname);
//删除
void mylink_delete(mylink *head,mylink p);
void free_node(mylink p);
//遍历(服务器端有时候会向所有的客户端发送更新等消息)
void mylink_travel(mylink *head,void (*vist)(mylink));
//销毁链表
void mylink_destory(mylink *head);

#endif

mylink.c 

#include <stdio.h>
#include <string.h>
#include "mylink.h"

//初始化链表
void mylink_init(mylink *head)
{
    *head = NULL;
}
//创建节点
mylink make_node(char *name,int fd)
{
    mylink p = (mylink)malloc(sizeof(struct node));
    strcpy(p->item,name);
    p->fifo_fd = fd;
    p->next = NULL;
}
//插入
void mylink_insert(mylink *head,mylink p)
{   
    //头插法
    p->next = *head;
    *head = p;
}
//查找
mylink mylink_search(mylink *head,const char *keyname)
{
    mylink p;
    for(p = *head;p != NULL;p = p->next)
    {
        if(strcmp(p->item,keyname) == 0)
        {
            return p;
        }
    }
    return NULL;
}
//删除
void mylink_delete(mylink *head,mylink p)
{
    mylink tmp;
    //如果在头节点
    if(*head == p)
    {
        *head = (*head)->next;
        return;
    }
    //如果不在头节点
    for(tmp = *head;tmp != NULL;tmp = tmp->next)
    {
        if(tmp != NULL && tmp->next == p)
        {
            tmp->next = p->next;
            return;
        }
    }
}
void free_node(mylink p)
{
    free(p);
}
//遍历(服务器端有时候会向所有的客户端发送更新等消息)
void mylink_travel(mylink *head,void (*vist)(mylink))
{
    mylink p;
    for(p = *head;p != NULL;p = p->next)
    {
        vist(p);
    }
}
//销毁链表
void mylink_destory(mylink *head)
{
    mylink p= *head, q;
    while (p != NULL) {
        q = p->next;
        free(p);
        p = q;
    }
    *head = NULL;
}

Pipeline design: The pipeline is half-duplex communication, so when we communicate with each other at both ends, it is best to design two pipelines, because when the client sends data to the server, the server is unified, and the pipeline It is a queue structure, so the client can write data to the same pipeline, and the server can read data sequentially from this pipeline. But when the server sends data to the client, it may send the data to A or B, so each client has an intermediate pipeline.

client:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include "qq_ipc.h"
#include "mylink.h"

//服务器端统一的管道
#define SERVER_FIFO  "SEV_FIFO"

void sys_error(const char *str)
{
    perror(str);
}


int main(int argc,char *argv[])
{

    if(argc < 2)
    {
        printf("./client name\n");
    }

    int server_fd,client_fd,flag,len;
    char cmdbuf[256];

    //打开文件向服务器中写数据的管道
    server_fd = open(SERVER_FIFO,O_NONBLOCK);
    if(server_fd == -1)
    {
        sys_error("open");
    }
    //创建一个专属自己管道
    mkfifo(argv[1],0777);

    //通知服务器我现在要登录了
    struct DATA_INFO cbuf,tmpbuf,talkbuf;
    cbuf.protocal = 1;
    //把管道的标识传给服务器端,服务器才能向里面写数据
    strcpy(cbuf.srcname,argv[1]);
    client_fd = open(argv[1],O_RDONLY|O_NOBLACK);
    //将管道设置为非阻塞方式
    flag = fcntl(STDIN_FILENO,F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(STDIN_FILENO,F_SETFL,flag);
    //将登录信息传给服务器
    write(server_fd,&cbuf,sizeof(cbuf));

    //信息交互
    while(1)
    {
        //判断要交互的客户端
        len = read(client_fd,&tmpbuf,sizeof(tmpbuf));
        if(len > 0)
        {
            if(tmpbuf.protocal == 3)
            {
                printf("%s id not online\n",tmpbuf.destname);
            }
            else if(tmpbuf.protocal == 2)
            {
                printf("%s : %s\n",tmpbuf.srcname,tmpbuf.destname);
            }
        }
        else if(len < 0)
        {
            if(errno != EAGAIN)//不是管道中没有数据
            {
                sys_err("client read\n");
            }
        }

        len = read(STDIN_FILENO,&cmdbuf,sizeof(cmdbuf)); //从键盘输入
        if(len >0)
        {
            char *dname,*databuf;
            memset(&talkbuf,0,sizeof(talkbuf));
            cmdbuf[len] = '\0';

            dname = strtok(cmdbuf, "#\n");                  /*按既定格式拆分字符串*/
            
            if (strcmp("exit", dname) == 0) {               /*退出登录:指定包号,退出者名字*/
                talkbuf.protocal = 4;
                strcpy(talkbuf.srcname, argv[1]);
                write(server_fd, &talkbuf, sizeof(talkbuf));/*将退出登录包通过公共管道写给服务器*/
                break;
            } else {
                talkbuf.protocal = 2;                       /*聊天*/
                strcpy(talkbuf.destname, dname);            /*填充聊天目标客户名*/
                strcpy(talkbuf.srcname, argv[1]);           /*填充发送聊天内容的用户名*/

                databuf = strtok(NULL, "\0");               
                strcpy(talkbuf.data, databuf);
            }
            write(server_fd, &talkbuf, sizeof(talkbuf));    /*将聊天包写入公共管道*/
        }
    }

    unlink(argv[1]);            /*删除私有管道*/
    close(client_fd);           /*关闭私有管道的读端(客户端只掌握读端)*/
    close(server_fd);           /*关闭公共管道的写端(客户端值掌握写端)*/
    return 0;
}

server.c

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include "qq_ipc.h"
#include "mylink.h"

#define SERVER_PROT "SEV_FIFO"              /*定义众所周知的共有管道*/

mylink head = NULL;                         /*定义用户描述客户端信息的结构体*/

void sys_err(char *str)
{
    perror(str);
    exit(-1);
}

/*有新用户登录,将该用户插入链表*/
int login_qq(struct DATA_INFO *buf, mylink *head)
{
    int fd;

    fd = open(buf->srcname, O_WRONLY);          /*获取登录者名字,以只写方式打开以其名字命名的私有管道*/
    mylink node = make_node(buf->srcname, fd);  /*利用用户名和文件描述符创建一个节点*/
    mylink_insert(head, node);                  /*将新创建的节点插入链表*/

    return 0;
}

/*客户端发送聊天,服务器负责转发聊天内容*/
void transfer_qq(struct DATA_INFO *buf, mylink *head)
{
    mylink p = mylink_search(head, buf->destname);      /*遍历链表查询目标用户是否在线*/
    if (p == NULL) {
        struct DATA_INFO lineout = {3};              /*目标用户不在, 封装3号数据包*/
        strcpy(lineout.destname, buf->destname);        /*将目标用户名写入3号包*/
        mylink q = mylink_search(head, buf->srcname);   /*获取源用户节点,得到对应私有管道文件描述符*/
        
        write(q->fifo_fd, &lineout, sizeof(lineout));   /*通过私有管道写给数据来源客户端*/
    } else
        write(p->fifo_fd, buf, sizeof(*buf));           /*目标用户在线,将数据包写给目标用户*/
}

/*客户端退出*/
int logout_qq(struct DATA_INFO *buf, mylink *head)
{
    mylink p = mylink_search(head, buf->srcname);       /*从链表找到该客户节点*/

    close(p->fifo_fd);                                  /*关闭其对应的私有管道文件描述符*/
    mylink_delete(head, p);                             /*将对应节点从链表摘下*/
    free_node(p);                                       /*释放节点*/
}

void err_qq(struct DATA_INFO *buf)
{
    fprintf(stderr, "bad client %s connect \n", buf->srcname);
}

int main(void)
{
    int server_fd;                                      /*公共管道文件描述符(读端)*/
    struct DATA_INFO dbuf;                           /*定义数据包结构体对象*/
    
    if (access(SERVER_PROT, F_OK) != 0) {               /*判断公有管道是否存在, 不存在则创建*/
        mkfifo(SERVER_PROT, 0664);
    }

    if ((server_fd = open(SERVER_PROT, O_RDONLY)) < 0)  /*服务器以只读方式打开公有管道一端*/
        sys_err("open");

    mylink_init(&head);                                 /*初始化链表*/

    while (1) {
        read(server_fd, &dbuf, sizeof(dbuf));           /*读取公共管道,分析数据包,处理数据*/
        switch (dbuf.protocal) {
            case 1: login_qq(&dbuf, &head); break;      
            case 2: transfer_qq(&dbuf, &head); break;   
            case 4: logout_qq(&dbuf, &head); break;
            default: err_qq(&dbuf);
        }
    }

    close(server_fd);
}

Guess you like

Origin blog.csdn.net/weixin_46120107/article/details/126446147