文件和消息转发服务端和qt搭建的网络聊天室客户端

文件和消息转发服务端和qt搭建的网络聊天室客户端

  • 文件和消息转发服务端使用boost::asio搭建
  • 均是异步proactor模型的网络服务端
  • 聊天室客户端使用qt搭建

实现功能:

  • 账号注册,登录,向已注册账号发送消息支持向离线账号发送消息待上线接收
  • 向已注册账号发送文件,(将文件发送至服务端,通知对方是否接受该文件)
  • 创建聊天室,其他账号通过聊天室账号加入到同一个聊天室聊天

github地址:https://github.com/t-r-n/chat-and-file-server-

github地址

客户端介绍

  • 首先是账号注册登陆界面
    文件和消息转发服务端和qt搭建的网络聊天室客户端_第1张图片
  • 界面整体布局和功能如下:
    文件和消息转发服务端和qt搭建的网络聊天室客户端_第2张图片
  • 填写对方已注册的账号发送消息,支持对方离线发送,上线接收
    文件和消息转发服务端和qt搭建的网络聊天室客户端_第3张图片
  • 在文件名栏填写待发送文件路径点击发送文件
    文件和消息转发服务端和qt搭建的网络聊天室客户端_第4张图片
  • 将文件名粘贴至文件名栏点击收文件接收文件
    文件和消息转发服务端和qt搭建的网络聊天室客户端_第5张图片
  • 点击"+“创建群聊 ,”++"加入群聊

文件和消息转发服务端和qt搭建的网络聊天室客户端_第6张图片

  • 点击列表中加入或创建的聊天室号即可聊天
    文件和消息转发服务端和qt搭建的网络聊天室客户端_第7张图片

服务端介绍 :

  • 因为文件收发会产生大量io影响消息转发的性能所以消息转发服务端和文件收发服务端独立

以下是消息服务端部分代码

使用自定义消息包头用于网络传输

    struct Head {
        unsigned char type;//消息类型
        unsigned int length;
        unsigned int id;//暂时用作群号
        int          packid;//
        unsigned int account;//账号
        unsigned int mima;//密码
        unsigned int sendto;//0给服务器 非0即要发给的账户
        unsigned int status = 0; //状态
    };

**boost::asio在windows下底层使用iocp, 读写由操作系统完成为proactor模式,Linux下底层是epoll,reactor模式 **

  • 该部分具体可参考网络io模型

实例化server类启动后socket监听连接事件,客户端连接后实例化一个客户对象并监听读事件
一下是读事件部分代码

void clint::on_read_plus() {//
    if (isdiascard) {//该客户端与服务端连接是否已断开
        return;
    }
    buf.clear(); 
    buf.resize(1024);
    auto self(shared_from_this());
    //操作系统从socket缓冲区异步的读取固定大小的数据,读够之后调用lambda回调函数进行下一步处理
    async_read(sock_, buffer(buf), transfer_exactly(sizehead), strand_.wrap([this, self](boost::system::error_code er, size_t sz) {
        if (er) {//发生错误
            int ret = errorhandle(er);
            if (ret == 0) {//发生错误时,本端未关闭,close释放内存
                sock_.close();
                islogout = true;
            }
            return;
        }
        Head h = getHead(buf);//获取buffer的包头
        int ret = headanylize((const Head*)buf.c_str());//分析包头
        if (h.length >= 0&&h.length<1024) {
            self->on_read_content(h); //根据包头中的length变量确定后续需要读取的大小
            return;
        }
    }));
}

void clint::on_read_content(Head he) {
    if (isdiascard) {
        return;
    }
    buf.clear(); 
    buf.resize(1024);
    auto self(shared_from_this());
    async_read(sock_, buffer(buf), transfer_exactly(he.length), strand_.wrap([this, self,he](boost::system::error_code er, size_t sz) {
        if (er) {//错误处理
            int ret = errorhandle(er);
            if (ret == 0) {
                sock_.close();
                islogout = true;
            }
            return;
        }
        int ret = headanylize((const Head*)&he);
        h = (Head*)&he;
        switch (ret) {
            case 1: //消息转发
			case 2://登陆验证
			...  //消息压入共享数据队列等待子线程转发,文件收发通知等功能
            default: {
                cout << __LINE__ << endl;
                break;
            }
        }
        self->on_read_plus();
    }));
}

该服务端使用到三个子线程:

void Server::translate() {//用于将消息压入待接收对象的消息队列
    static queue tmpMesQueue;
    static Head h;
    while (1) {
        {
            lock_guardacc_mutex_lock(acc_mutex);
            for (auto& a : account) {
                unique_locksemu_lock(a.second->semu, std::defer_lock);
                if (semu_lock.try_lock()) {
                    if (a.second->semessage.size() > 0) {
                        tmpMesQueue.push( a.second->semessage.front() );
                        a.second->semessage.pop();
                    }
                    semu_lock.unlock();
                }
                else {
                }
            }
            int size = tmpMesQueue.size();
            while (!tmpMesQueue.empty()) {
                string s = tmpMesQueue.front();
                tmpMesQueue.pop();
                h = getHead(s);
                if (account.find(h.sendto) != account.end()) {
                    unique_lockremu_lock(account[h.sendto]->remu,std::defer_lock);
                    if (remu_lock.try_lock()) {
                        account[h.sendto]->remessage.push(s);
                    }
                    else {//没拿到锁,放回消息队列
                        tmpMesQueue.push(s);
                    }
                }else {//不存在对方账户
                    cout << "未找到用户:" << h.sendto << endl;
                }
                if ((--size) <= 0)break;
            }
        }

    }
}

void Server::handle_login_write() {//若目标客户端在线就发送消息队列中的消息
    while (1) {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));//睡眠10毫秒
        unique_lockll(cur_account_ptr_mutex, std::defer_lock);
        if (ll.try_lock()) {
            for (auto &a : cur_account_ptr) {
                if (cur_account_ptr[a.first] && !cur_account_ptr[a.first]->isdiascard) {//如果该指针不为空说明当前用户在线
                    unique_locklll(cur_account_ptr[a.first]->clch->remu, std::defer_lock);//如果接收队列可以访问   
                    if (lll.try_lock()) {
                        if (cur_account_ptr[a.first]->clch->remessage.size() > 0) {      
                            lll.unlock();//先解锁否则on_write那边没法取任务
                            cur_account_ptr[a.first]->on_write();//如果当前要发送的用户在线
                        }
                    }
                }
            }
        }
    }
}

void Server::gc() {//用于客户端下线清理内存
    while (1) {
        this_thread::sleep_for(std::chrono::milliseconds(100));
        unique_lockgc_lock(cur_account_ptr_mutex, std::defer_lock);
        if (gc_lock.try_lock()) {
            for (auto it = cur_account_ptr.begin(); it != cur_account_ptr.end(); ) {
                if ((*it).second)
                    if ((*it).second->isdiascard) {
                        cout << "id:" << (*it).second->id << "已被清理" << endl;
                        cur_account_ptr.erase(it++);
                    }
                    else {
                        ++it;
                    }
                else {
                    ++it;
                }
            }
        }
        else {
        }
    }
}

文件服务器代码详见github

你可能感兴趣的:(qt,网络,linux,c++,服务器)