//下面是消息的头文件
#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;
}
本文详细介绍了一个基于Boost.Asio的聊天室系统的设计与实现,包括服务器与客户端的交互流程,消息封装与解析机制,以及多线程处理和错误处理策略。
1386

被折叠的 条评论
为什么被折叠?



