c++群聊服务器(TCP/IP)

群聊服务器简单描述
客户端发送消息 到服务器,服务器转发消息给每个客户端、

boost库安装可以去官网,也可以去看我的另一篇博文
c++回声服务器,上面有我给出的boost库 百度云盘

boost库的案例实现
我加入部分注释,方便理解。
首先是 客户端和服务器都共同要用的头文件
chat_message.hpp

//
// chat_message.hpp
// ~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef CHAT_MESSAGE_HPP
#define CHAT_MESSAGE_HPP

#include <cstdio>       
#include <cstdlib>
#include <cstring>

/*
聊天消息类
定义了两个私有成员变量
    1.data_                 缓冲区(大小为  消息头长度 + 最大消息主体长度 = 4 + 512)
    2.body_length        消息主体的长度

以及两个共有枚举
    1.header_length             消息头长度(固定4个字节,头部这个4个字节用来记录消息内容的长度)
    2.max_body_length         最大消息主体长度(消息长度最长设置为512字节)

公有方法
    1.返回缓存区的指针
    2.返回缓存区的大小
    3.返回消息的缓冲区指针
    4.返回消息的长度(不包括头部)
    5.设置消息的长度(如果超过最大长度,则设置为最大长度)
    6.解码头部同时设置消息的长度
    7.解码头部同时设置缓冲区的头部(即前4个字节)
*/ 
class chat_message
{
public:
  enum { header_length = 4 };
  enum { max_body_length = 512 };

  chat_message()
    : body_length_(0)
  {
  }

  const char* data() const
  {
    return data_;
  }

  char* data()
  {
    return data_;
  }

  std::size_t length() const
  {
    return header_length + body_length_;
  }

  const char* body() const
  {
    return data_ + header_length;
  }

  char* body()
  {
    return data_ + header_length;
  }

  std::size_t body_length() const
  {
    return body_length_;
  }

  void body_length(std::size_t new_length)
  {
    body_length_ = new_length;
    if (body_length_ > max_body_length)
      body_length_ = max_body_length;
  }

  bool decode_header()
  {
    char header[header_length + 1] = "";
    std::strncat(header, data_, header_length);
    body_length_ = std::atoi(header);
    if (body_length_ > max_body_length)
    {
      body_length_ = 0;
      return false;
    }
    return true;
  }

  void encode_header()
  {
    char header[header_length + 1] = "";
    std::sprintf(header, "%4d", static_cast<int>(body_length_));
    std::memcpy(data_, header, header_length);
  }

private:
  char data_[header_length + max_body_length];
  std::size_t body_length_;
};

#endif // CHAT_MESSAGE_HPP

服务器的实现

//
// chat_server.cpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#define _CRT_SECURE_NO_WARNINGS
#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;

/*
文件内容大纲
    1.别名2个
        chat_message_queue;             聊天消息的队列容器
        chat_participant_ptr                聊天参与者的共享指针

    2.定义了4个类
        chat_participant(抽象类)            聊天参与者的抽象类,定义了1个接口 deliver()消息发送 ,以及基本礼貌 虚析构函数
        chat_room(聊天房间)                 管理聊天参与者的加入和离开,还有聊天消息的记录
        chat_session(聊天会话)              继承了聊天参与者类,以及共享启用类
        chat_server(聊天服务器)             运行每一个聊天会话

 
 chat_room(聊天房间) 
    定义了2个私有成员变量
        1.participants_             聊天参与者的容器(set容器 聊天参与者的共享指针)   
        2.recent_msgs_            最近消息队列 

    定义了1个私有枚举
        1.max_recent_msgs       限制最近消息为100条
    
    定义了3个共有方法
        1.连接给每个新加入的参与者推送 队列中的消息
        2.参与者离开时从聊天参与者容器中移除
        3.新消息记录到聊天消息队列中,然后给每个参与者发送此消息


chat_session(聊天会话)
    定义了4个私有成员
        1.socket_               tcp的套接字
        2.room_                 聊天房间的引用
        3.read_msg_           读取聊天消息 
        4.write_msgs_         自身的消息队列

    定义了2个共有方法
        1.开始会话将自身的共享指针传递给聊天房间加入聊天,然后调用自身私有方法do_read_header()
        2.发送消息,先检查自身消息是否为空,接着将消息压入消息队列,再根据之前的检查,如果消息为空 则调用 私有方法do_write()  
                 ps: 吐槽一下,这两次 非运算有点意义不明,可能是作者的习惯所致
     
    定义了3个私有方法
        1.读取消息头部,没有错误和成功解码到消息长度则调用私有方法do_read_body(),调用聊天房间离开方法,将自身的共享指针传进去
        2.读取消息,没有错误则调用聊天房间的发送消息把消息传进去,接着调用读取头部,否则调用聊天房间离开方法,将自身的共享指针传进去
        3.写入消息,没有错误则调用弹出第一个消息,在判断你消息队列是否已经为空,不为空则继续调用写消息。
                                                        否则产生错误,就调用聊天房间的离开方法,将自身共享指针传进去


 chat_server(聊天服务器)
    定义2个私有成员变量
        1.acceptor_                 客户端套接字接收
        2.room_                       聊天房间

    定义了1个私有方法
        1.接收客户端的套接字,没有错误创建一个会话,传递进去套接字和聊天房间,并且开启会话,然后调用自身继续等待客户端接入



代码分析:           ps: 博主的分析,不保证准确无误吧。看官们,看看就好。
   
            1.聊天房间要做的事:
                1.记录参与者,谁加入了,谁离开了。
                2.记录消息记录,对每个参与者进来就把所记录的消息发送给它。
                3.转发每个客户端会话的消息分发给其他客户端会话。

            2.客户端会话(聊天会话)要做的事:
                1.加入聊天房间
                2.接收聊天房间发过来的消息
                3.发送消息到聊天房间

    
    这样就会遇到一个问题,客户端会话需要持有聊天房间的接口,聊天房间需要持有客户端会话的接口。

    这样就会遇到一个语法上的限制:
          定义类型是有先后顺序之分的,后定义的类型可以持有先定义的类型作为接口。
          先定义的类型是没办法,获取后面定义的类型进行操作的。

    这样就需要一个定义在聊天房间之前的类型,做接口使用、这个接口,作者定义为 聊天参与者。
     
     这个类本身没有必要实体化,其作用就是为了 让聊天房间能够通过这个可操作对象 与 聊天会话进行通信。

     这样关系它们大概是这样的
              聊天消息 -> 聊天参与者(抽象类) -> 聊天房间 -> 聊天会话 -> 聊天服务器


    首先我要明确一点,我说的可操作对象是什么
    举例:                
                由于聊天消息 和 聊天参与者定义在聊天房间之前。
                聊天房间可以将,聊天消息 和 聊天参与者,定义为成员变量也可以定义成接口参数 也就可操作的对象。
                反过来却不行,因为那个时候聊天房间没有诞生呢。
                对于聊天会话来说,聊天房间是可操作的,但是聊天房间只能操作 聊天参与者。
                那么让聊天会话,继承聊天参与者。聊天房间就能操作到 聊天参与者了。

  
   解释部分,少见的疑难杂点。
        enable_shared_from_this 类型
        当一个类,用到共享指针,并且还要通过这个类的返回或者获取到,这个类的共享指针,就需要继承这个类。
        不能这样 
        struct Bad
        {
            // 错误写法:用不安全的表达式试图获得 this 的 shared_ptr 对象
            std::shared_ptr<Bad> getptr() {
                return std::shared_ptr<Bad>(this);
            }
            ~Bad() { std::cout << "Bad::~Bad() called\n"; }
        };

        应该这样
        struct Good: std::enable_shared_from_this<Good> // 注意:继承
        {
            std::shared_ptr<Good> getptr() {
                return shared_from_this();
            }
        };

        shared_from_this()  返回共享 *this 所有权的 shared_ptr。 


        !ec     这个是 操作符重载函数  !
*/

//----------------------------------------------------------------------

typedef std::deque<chat_message> chat_message_queue;

//----------------------------------------------------------------------

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
{
public:
  void join(chat_participant_ptr participant)
  {
    participants_.insert(participant);
    for (auto msg: recent_msgs_)
      participant->deliver(msg);
  }

  void leave(chat_participant_ptr participant)
  {
    participants_.erase(participant);
  }

  void deliver(const chat_message& msg)
  {
    recent_msgs_.push_back(msg);
    while (recent_msgs_.size() > max_recent_msgs)
      recent_msgs_.pop_front();

    for (auto participant: participants_)
      participant->deliver(msg);
  }

private:
  std::set<chat_participant_ptr> participants_;
  enum { max_recent_msgs = 100 };
  chat_message_queue recent_msgs_;
};

//----------------------------------------------------------------------

class chat_session
  : public chat_participant,
    public std::enable_shared_from_this<chat_session>
{
public:
  chat_session(tcp::socket socket, chat_room& room)
    : socket_(std::move(socket)),
      room_(room)
  {
  }

  void start()
  {
    room_.join(shared_from_this());
    do_read_header();
  }

  void deliver(const chat_message& msg)
  {
    bool write_in_progress = !write_msgs_.empty();
    write_msgs_.push_back(msg);
    if (!write_in_progress)
    {
      do_write();
    }
  }

private:
  void do_read_header()
  {
    auto self(shared_from_this());
    boost::asio::async_read(socket_,
        boost::asio::buffer(read_msg_.data(), chat_message::header_length),
        [this, self](boost::system::error_code ec, std::size_t /*length*/)
        {
          if (!ec && read_msg_.decode_header())
          {
            do_read_body();
          }
          else
          {
            room_.leave(shared_from_this());
          }
        });
  }

  void do_read_body()
  {
    auto self(shared_from_this());
    boost::asio::async_read(socket_,
        boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
        [this, self](boost::system::error_code ec, std::size_t /*length*/)
        {
          if (!ec)
          {
            room_.deliver(read_msg_);
            do_read_header();
          }
          else
          {
            room_.leave(shared_from_this());
          }
        });
  }

  void do_write()
  {
    auto self(shared_from_this());
    boost::asio::async_write(socket_,
        boost::asio::buffer(write_msgs_.front().data(),
          write_msgs_.front().length()),
        [this, self](boost::system::error_code ec, std::size_t /*length*/)
        {
          if (!ec)
          {
            write_msgs_.pop_front();
            if (!write_msgs_.empty())
            {
              do_write();
            }
          }
          else
          {
            room_.leave(shared_from_this());
          }
        });
  }

  tcp::socket socket_;
  chat_room& room_;
  chat_message read_msg_;
  chat_message_queue write_msgs_;
};

//----------------------------------------------------------------------

class chat_server
{
public:
  chat_server(boost::asio::io_context& io_context,
      const tcp::endpoint& endpoint)
    : acceptor_(io_context, endpoint)
  {
    do_accept();
  }

private:
  void do_accept()
  {
    acceptor_.async_accept(
        [this](boost::system::error_code ec, tcp::socket socket)
        {
          if (!ec)
          {
            std::make_shared<chat_session>(std::move(socket), room_)->start();
          }

          do_accept();
        });
  }

  tcp::acceptor acceptor_;
  chat_room room_;
};

//----------------------------------------------------------------------

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

    boost::asio::io_context io_context;

    std::list<chat_server> servers;
    for (int i = 1; i < argc; ++i)
    {
      tcp::endpoint endpoint(tcp::v4(), std::atoi(argv[i]));
      servers.emplace_back(io_context, endpoint);
    }

    io_context.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}

客户端代码

//
// chat_client.cpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#define _CRT_SECURE_NO_WARNINGS
#include <cstdlib>
#include <deque>
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
#include "chat_message.hpp"

using boost::asio::ip::tcp;

/*
文件内大纲
    1.别名1个
        chat_message_queue          聊天消息队列

    1.定义了一个类
        chat_client                        聊天客户端

 chat_client(聊天客户端)
    定义了4个私有成员变量
        1.io_context_                   IO上下文引用
        2.read_msg_                     聊天消息
        3.socket_                         套接字
        4.write_msgs_                   消息队列

    定义了2个共有方法
        1.先判断消息队列是否为空,然后将消息放入消息队列中,接着判断如果消息队列为空则调用私有方法do_write()
        2.关闭套接字

    定义了4个私有方法
        1.连接到服务器
        2.读取消息头部,没有错误则调用私有方法do_read_body(),否则关闭套接字
        3.读取消息,没有错误则将信息打印到控制台,然后调用私有方法do_read_header(),否则关闭套接字
        4.将消息发送到服务器,没有错误 ,则弹出第一个已经写完的消息,判断如果还有消息继续调用自身,否则关闭套接字


*/

typedef std::deque<chat_message> chat_message_queue;

class chat_client
{
public:
  chat_client(boost::asio::io_context& io_context,
      const tcp::resolver::results_type& endpoints)
    : io_context_(io_context),
      socket_(io_context)
  {
    do_connect(endpoints);
  }

  void write(const chat_message& msg)
  {
    boost::asio::post(io_context_,
        [this, msg]()
        {
          bool write_in_progress = !write_msgs_.empty();
          write_msgs_.push_back(msg);
          if (!write_in_progress)
          {
            do_write();
          }
        });
  }

  void close()
  {
    boost::asio::post(io_context_, [this]() { socket_.close(); });
  }

private:
  void do_connect(const tcp::resolver::results_type& endpoints)
  {
    boost::asio::async_connect(socket_, endpoints,
        [this](boost::system::error_code ec, tcp::endpoint)
        {
          if (!ec)
          {
            do_read_header();
          }
        });
  }

  void do_read_header()
  {
    boost::asio::async_read(socket_,
        boost::asio::buffer(read_msg_.data(), chat_message::header_length),
        [this](boost::system::error_code ec, std::size_t /*length*/)
        {
          if (!ec && read_msg_.decode_header())
          {
            do_read_body();
          }
          else
          {
            socket_.close();
          }
        });
  }

  void do_read_body()
  {
    boost::asio::async_read(socket_,
        boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
        [this](boost::system::error_code ec, std::size_t /*length*/)
        {
          if (!ec)
          {
            std::cout.write(read_msg_.body(), read_msg_.body_length());
            std::cout << "\n";
            do_read_header();
          }
          else
          {
            socket_.close();
          }
        });
  }

  void do_write()
  {
    boost::asio::async_write(socket_,
        boost::asio::buffer(write_msgs_.front().data(),
          write_msgs_.front().length()),
        [this](boost::system::error_code ec, std::size_t /*length*/)
        {
          if (!ec)
          {
            write_msgs_.pop_front();
            if (!write_msgs_.empty())
            {
              do_write();
            }
          }
          else
          {
            socket_.close();
          }
        });
  }

private:
  boost::asio::io_context& io_context_;
  tcp::socket socket_;
  chat_message read_msg_;
  chat_message_queue write_msgs_;
};

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

    boost::asio::io_context io_context;

    tcp::resolver resolver(io_context);
    auto endpoints = resolver.resolve(argv[1], argv[2]);
    chat_client c(io_context, endpoints);

    std::thread t([&io_context](){ io_context.run(); });

    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();
    t.join();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}

运行效果
效果
有缘在更…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值