boost::asio TCP socket聊天室

本文详细介绍了一个基于Boost.Asio的聊天室系统的设计与实现,包括服务器与客户端的交互流程,消息封装与解析机制,以及多线程处理和错误处理策略。

//下面是消息的头文件
#ifndef chat_message_hpp                                                                                                      
#define chat_message_hpp                                                                                                      
                                                                                                                              
#include <cstdio>                                                                                                             
#include <cstdlib>                                                                                                            
#include <cstring>                                                                                                            
                                                                                                                              
//承载内容的消息包的类:包含header和body(类似于http协议),header包含了body相关属性,比如这里用固定的4个字节保存了body中字节
个数                                                                                                                          
class chat_message {                                                                                                          
                                                                                                                              
public:                                                                                                                       
    enum {header_length = 4};//固定的4个字节                                                                                  
    enum {max_body_length = 512};//body最大字节个数                                                                           
                                                                                                                              
    chat_message():m_body_length(0), m_data(""){}
    ~chat_message(){}
    
    //信息字节数组
    char* data(){return m_data;};
    
    //信息字节数组中有效长度,即头+体的总长度
    std::size_t length(){return header_length + m_body_length;}
    
    //获取指向包体的指针
    char* body(){return m_data + header_length;}

    //获取包体长度
    std::size_t body_length(){return m_body_length;}
    
    //设置包体长度
    void body_length(std::size_t new_length){
        m_body_length = new_length;
        if (m_body_length > max_body_length) {
            m_body_length = max_body_length;
        }
    }
    
    //解码头信息
    bool decoder_header(){
        char header[header_length + 1] = "";
        std::strncat(header, m_data, header_length);
        m_body_length = std::atoi(header);
        if (m_body_length > max_body_length) {
            m_body_length = 0;
            return false;
        }
        return true;
    }
    
    //编码头信息
    void encode_header(){
        char header[header_length + 1] = "";
        std::sprintf(header, "%4d", static_cast<int>(m_body_length));
        std::memcpy(m_data, header, header_length);
    }
    
private:
    std::size_t m_body_length;//body长度,即字节个数
    char m_data[header_length + max_body_length];//用来存放消息:固定的前4个字节保存了body中字节个数,接着是body信息
};

#endif /* chat_message_hpp */


//下面是消息的源文件
//  chat_message.cpp                                                                                                          
//  ServerOfChat                                                                                                              
//                                                                                                                            
//  Created by ma qianli on 2020/3/4.                                                                                         
//  Copyright © 2020 qianli. All rights reserved.                                                                             
//                                                                                                                            
                                                                                                                              
#include "chat_message.hpp"  

//下面是服务器源文件
#include <cstdlib>                                                                                                            
#include <deque>                                                                                                              
#include <iostream>                                                                                                           
#include <list>                                                                                                               
#include <memory>                                                                                                             
#include <set>                                                                                                                
#include <utility>                                                                                                            
#include <boost/asio.hpp>                                                                                                     
                                                                                                                              
#include "chat_message.hpp"                                                                                                   
                                                                                                                              
using boost::asio::ip::tcp;                                                                                                   
                                                                                                                              
//deque先进先出(在建立vector容器时,一般来说伴随这建立空间->填充数据->重建更大空间->复制原空间数据->删除原空间->添加新数据,
如此反复,保证vector始终是一块独立的连续内存空间;在建立deque容器时,一般便随着建立空间->建立数据->建立新空间->填充新数据,如
此反复,没有原空间数据的复制和删除过程,是由多个连续的内存空间组成的。)                                                      
using chat_message_queue = std::deque<chat_message>;                                                                          
                                                                                                                              
//聊天成员                                                                                                                    
class chat_participant {                                                                                                      
public:                                                                                                                       
    virtual ~chat_participant(){}                                                                                             
    virtual void deliver(const chat_message &msg) = 0;                                                                        
};                                                                                                                            
                                                                                                                              
typedef std::shared_ptr<chat_participant> chat_participant_ptr;                                                               
                                                                                                                              
//聊天室                                                                                                                      
class chat_room {                                                                                                             
    //聊天室中所有成员                                                                                                        
    std::set<chat_participant_ptr> m_participants;                                                                            
    //历史消息(最多100条)                                                                                                     
    enum{max_recent_msgs = 100};                                                                                              
    chat_message_queue m_recent_msgs;                                                                                         
                                                                                                                              
public:                                                                                                                       
    //成员进入聊天室                                                                                                          
    void join(chat_participant_ptr participant){                                                                              
        m_participants.insert(participant);                                                                                   
        for (auto &msg : m_recent_msgs) {                                                                                     
            participant->deliver(msg);                                                                                        
        }                                                                                                                     
    }
    
    //成员离开聊天室
    void leave(chat_participant_ptr participant){
        m_participants.erase(participant);
    }
    
    //传递消息
    void deliver(const chat_message &msg){
        m_recent_msgs.push_back(msg);//压入尾部
        while (m_recent_msgs.size() > max_recent_msgs) {//保证100条
            m_recent_msgs.pop_front();
        }
        //将消息分发给所有成员
        for (auto participant : m_participants) {
            participant->deliver(msg);
        }
    }
    
};

//与客户端的聊天会话,即聊天成员(当接受客户端发送过来的请求后,就会创建一个chat_session,用它读写消息)
class chat_session : public chat_participant, public std::enable_shared_from_this<chat_session>{

    tcp::socket m_socket;//与客户端建立连接的socket
    chat_room &m_room;  //所属聊天室
    chat_message m_read_msg;//当前消息
    chat_message_queue m_write_msgs;//写给客户端的消息队列
public:
    chat_session(boost::asio::io_service &io_service, chat_room &room):
        m_socket(io_service), m_room(room){

    }
    
    tcp::socket& socket(){
        return m_socket;
    }
                    
    void start(){
        m_room.join(shared_from_this());//聊天室增加成员
        do_read_header();//读取成员传递过来的消息头
    }
    
    virtual void deliver(const chat_message &msg){
        //第一次调用时为false
        bool write_in_progress = !m_write_msgs.empty();
        
        //存放写给客户端的消息队列
        m_write_msgs.push_back(msg);
        
        //防止同时多次调用
        if (!write_in_progress) {
            do_write();//将历史消息写给客户端
        }
    }
    
    //读取成员传递过来的消息头
    void do_read_header(){
        auto sharedFromThis = shared_from_this();
        //异步读取4个字节
        boost::asio::async_read(m_socket,
                                boost::asio::buffer(m_read_msg.data(), chat_message::header_length),
                                [this, sharedFromThis](boost::system::error_code ec, std::size_t lenght){
            if (!ec && m_read_msg.decoder_header()) {
                do_read_body();
            }else{
                m_room.leave(sharedFromThis);
            }
            
        });
    }
    
    //读取成员传递过来的消息体
    void do_read_body(){
        auto sharedFromThis = shared_from_this();
        boost::asio::async_read(m_socket,
                                boost::asio::buffer(m_read_msg.body(), m_read_msg.body_length()),
                                [this, sharedFromThis](boost::system::error_code ec, std::size_t lenght){
            if (!ec) {
                m_room.deliver(m_read_msg);
                //继续读取下一条消息的头
                do_read_header();
            }else{
                m_room.leave(sharedFromThis);
            }
        });
    }
    
    //将m_write_msgs中的第一条写给客户端
    void do_write(){
        auto sharedFromThis = shared_from_this();
        boost::asio::async_write(m_socket,
                                 boost::asio::buffer(m_write_msgs.front().data(), m_write_msgs.front().length()),
                                 [this, sharedFromThis](boost::system::error_code ec, std::size_t length){
            if (!ec) {
                m_write_msgs.pop_front();
                if (!m_write_msgs.empty()) {
                    do_write();
                }
            }else{
                m_room.leave(sharedFromThis);
            }
        });
    }
};

//聊天服务器,当接受客户端发送过来的请求后,就会创建一个chat_session,用它读写消息
class chat_server {
    
    boost::asio::io_service &m_io_service;
    tcp::acceptor m_acceptor;
    //tcp::socket m_socket;//为客户端提供的,便于建立socket连接(服务端socket<---->客户端socket)
    chat_room m_room;//每个chat_server都有一个room
    
public:
    chat_server(boost::asio::io_service &io_service,
                const tcp::endpoint &endpoint):m_io_service(io_service),
    m_acceptor(io_service, endpoint){
        do_accept();
    }
    
    //异步接受客户端的请求
    void do_accept(){
        auto session = std::make_shared<chat_session>(m_io_service, m_room);
        
        m_acceptor.async_accept(session->socket(), [this, session](boost::system::error_code ec){
            if(!ec){
                session->start();
            }
            
            do_accept();
        });
    }
    
};

int main(int argc, const char * argv[]) {
    
    try {
        if (argc < 2) {
            std::cerr << "Usage: chat_server <port> [<port>...]\n";
            return 1;
        }

        boost::asio::io_service io_service;

        std::list<chat_server*> servers;
        for (int i = 1; i < argc; ++i) {
            tcp::endpoint endpoint(tcp::v4(), std::atoi(argv[i]));
            servers.push_back(new chat_server(io_service, endpoint));//注意容器里保存的指针,程序最后要负责释放所指向的堆
        }
        
        io_service.run();
        
    } catch (std::exception &e) {
        std::cout << e.what() << std::endl;
    }
    
    return 0;
}


//下面是客户端源文件
#include <cstdlib>                                                                                                            
#include <deque>                                                                                                              
#include <iostream>                                                                                                           
#include <thread>                                                                                                             
#include <boost/asio.hpp>                                                                                                     
                                                                                                                              
#include "chat_message.hpp"                                                                                                   
                                                                                                                              
using boost::asio::ip::tcp;                                                                                                   
                                                                                                                              
using chat_message_queue = std::deque<chat_message>;                                                                          
                                                                                                                              
class chat_client{                                                                                                            
    boost::asio::io_service &m_io_service;                                                                                    
    tcp::socket m_socket;                                                                                                     
    chat_message m_read_msg;                                                                                                  
    chat_message_queue m_write_msgs;
public:
    chat_client(boost::asio::io_service &io_service,
                tcp::resolver::iterator endpoint_iterator):
    m_io_service(io_service), m_socket(io_service){
        do_connect(endpoint_iterator);
    }
    
    void write(const chat_message &msg){
        //让lambda在m_io_service的run所在线程执行(类似于OC中的performSelector: onThread: withObject: waitUntilDone: modes:)
        m_io_service.post([this, msg](){
            bool write_in_progress = !m_write_msgs.empty();
            m_write_msgs.push_back(msg);
            
            if (!write_in_progress) {
                do_write();
            }
        });
    }
    
    void close(){
        m_io_service.post([this](){
            m_socket.close();
        });
    }
    
    void do_connect(tcp::resolver::iterator endpoint_iterator){
        boost::asio::async_connect(m_socket, endpoint_iterator,
                                   [this](boost::system::error_code ec, tcp::resolver::iterator it){
            if (!ec) {
                do_read_header();
            }
        });
    }
    
    void do_read_header(){
        boost::asio::async_read(m_socket,
                                boost::asio::buffer(m_read_msg.data(), chat_message::header_length),
                                [this](boost::system::error_code ec, std::size_t lenght){
            if (!ec && m_read_msg.decoder_header()) {
                do_read_body();
            }else{
                m_socket.close();
            }
        });
    }
    
    void do_read_body(){
        boost::asio::async_read(m_socket,
                                boost::asio::buffer(m_read_msg.body(), m_read_msg.body_length()),
                                [this](boost::system::error_code ec, std::size_t length){
            if (!ec) {
                std::cout.write(m_read_msg.body(), m_read_msg.body_length());
                std::cout << std::endl;
                
                //继续读取下一条信息的头
                do_read_header();
                
            }else{
                m_socket.close();
            }
        });
    }
    
    void do_write(){
        boost::asio::async_write(m_socket,
                                 boost::asio::buffer(m_write_msgs.front().data(), m_write_msgs.front().length()),
                                 [this](boost::system::error_code ec, std::size_t length){
            if (!ec) {
                m_write_msgs.pop_front();
                if (!m_write_msgs.empty()) {
                    do_write();
                }
            }else{
                m_socket.close();
            }
        });
    }
    
};

int main(int argc, const char * argv[]) {
    
    try {
        if (argc != 3) {
            std::cerr << "Usage: chat_client <host> <port>\n";
            return 1;
        }

        boost::asio::io_service io_service;
        tcp::resolver resolver(io_service);
        auto endpoint_iterator = resolver.resolve({argv[1], argv[2]});
        chat_client c(io_service, endpoint_iterator);

        std::thread t([&io_service](){
            io_service.run();//这样,与io_service绑定的事件源的回调均在子线程上执行(这里指的是boost::asio::async_xxx中的lambda函数)。
        });

        char line[chat_message::max_body_length + 1] = "";
        while (std::cin.getline(line, chat_message::max_body_length + 1)) {
            chat_message msg;
            msg.body_length(std::strlen(line));
            std::memcpy(msg.body(), line, msg.body_length());
            msg.encode_header();
            c.write(msg);
        }

        c.close();//这里必须close,否则子线程中run不会退出,因为boost::asio::async_read事件源一直注册在io_service中.
        t.join();

    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }
    
    return 0;
}



 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值