在线聊天室 服务器端程序,使用boost::asio库实现多人在线的网络聊天室

技术原理

1、消息协议体设计得非常简单。只有一个header_length和body_length。header里面存数据长度。数据长度不能超过512字节

2、服务端使用一个std::deque数据结构管理历史消息。在聊天室中管理历史消息。当有新用户加入时,先把历史消息发给新用户。

3、服务端读到数据时,使用聊天室对象,转发给各个客户

4、聊天室对象使用std::set管理客户

程序代码如下,

server.cpp

#include "chat_message.h"

#include

#include

#include

#include

#include

#include

#include

#include

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

using chat_message_queue = std::deque;

class chat_session;

using chat_session_ptr = std::shared_ptr;

// 聊天室类的声明

class chat_room {

public:

void join(chat_session_ptr);

void leave(chat_session_ptr);

void deliver(const chat_message&);

private:

std::set participants_;

enum { max_recent_msgs = 100 };

chat_message_queue recent_msgs_;

};

class chat_session: public std::enable_shared_from_this {

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);

// 为了保护do_write线程里面的deque,避免两个线程同时写

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) {

// 头部解析成功,获取到body_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) {

// 如果读取消息成功,没有error

if(!ec) {

// room_的deliver msg,会先更新recent_message queue,

// 然后调用各个Session的Deliver message

// 将消息发给对应的client

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_;

// room的生命周期必须长于session的生命周期,

// 否则会因为持有无效的引用而翻车

chat_room& room_;

chat_message read_msg_;

chat_message_queue write_msgs_;

};

void chat_room::join(chat_session_ptr participant) {

participants_.insert(participant);

// 给新加入者广播一遍历史消息

for(const auto& msg: recent_msgs_) {

participant->deliver(msg);

}

}

void chat_room::leave(chat_session_ptr participant) {

participants_.erase(participant);

}

// 消息分发函数

void chat_room::deliver(const chat_message& msg) {

recent_msgs_.push_back(msg);

// recent_msgs_调整到最大值

while(recent_msgs_.size() > max_recent_msgs) {

recent_msgs_.pop_front();

}

// 给每个群聊参与者群发消息

for(auto & participant: participants_) {

participant->deliver(msg);

}

}

class chat_server {

public:

chat_server(boost::asio::io_service& io_service,

const tcp::endpoint& endpoint): acceptor_(io_service, endpoint),

socket_(io_service){

do_accept();

}

// 接收来自客户端的连接的函数

void do_accept() {

acceptor_.async_accept(

socket_,

[this] (boost::system::error_code ec) {

// 如果接收连接成功,没有错误

if(!ec) {

auto session = std::make_shared(std::move(socket_),

room_

);

session->start();

}

// 无论成功或失败,都继续接收连接

do_accept();

}

);

}

private:

tcp::acceptor acceptor_;

tcp::socket socket_;

chat_room room_;

};

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

try {

if(argc < 2) {

std::cerr << "Usage: chat_server [ ...]" << std::endl;

return 1;

}

boost::asio::io_service io_service;

std::list servers;

for(int i=1; i

tcp::endpoint endpoint(tcp::v4(), std::atoi(argv[i]));

servers.emplace_back(io_service, endpoint);

}

io_service.run();

}catch(std::exception& e) {

std::cerr << "Exception: " << e.what() << std::endl;

}

return 0;

}

client.cpp

#include "chat_message.h"

#include

#include

#include

#include

#include

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

using chat_message_queue = std::deque;

class chat_client {

public:

chat_client(boost::asio::io_service& io_service,

tcp::resolver::iterator endpoint_iterator

): io_service_(io_service), socket_(io_service) {

do_connect(endpoint_iterator);

}

void write(const chat_message& msg) {

// write是由主线程往子线程写东西

// 所以需要使用post提交到子线程运行

// 使得所有io操作都由io_service的子线程掌握

io_service_.post(

[this, msg] () {

bool write_in_progress = !write_msgs_.empty();

write_msgs_.push_back(msg);

if(!write_in_progress) {

do_write();

}

}

);

}

void close() {

io_service_.post(

[this] () {

socket_.close();

}

);

}

private:

void do_connect(tcp::resolver::iterator endpoint_iterator) {

boost::asio::async_connect(

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(

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()) {

// 如果没有错误,并且Decode_header成功,成功读取到body_length

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函数串联起事件链,接着读

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();

}

}

);

}

// 注意使用了引用类型,

// io_service对象的生命周期必须要大于chat_client对象的生命周期

// 否则会出现引用失效,导致异常

boost::asio::io_service& io_service_;

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 " << std::endl;

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(); });

char line[chat_message::max_body_length + 1];

// Ctrl + D 正常退出一个应用程序

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& ex) {

std::cerr << "Exception: " << ex.what() << std::endl;

}

return 0;

}

chat_message.h

#ifndef _CHAT_MESSAGE_H_

#define _CHAT_MESSAGE_H_

#include

#include

#include

class chat_message {

public:

enum { header_length = 4 };

enum { max_body_length = 512 };

chat_message():body_length_(0) {}

// 这里返回的data不可以修改

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

char* data() { return data_; }

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

// body为 data_往后面移动 head_length个字节

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

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

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

// 这里会有问题,默认只让读512字节,如果我要读513字节怎么办?

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(body_length_));

std::memcpy(data_, header, header_length);

}

private:

char data_[header_length+max_body_length];

std::size_t body_length_;

};

#endif

CMakeLists.txt

cmake_minimum_required(VERSION 2.6)

project(chat_room)

add_definitions(-std=c++14)

find_package(Boost REQUIRED COMPONENTS

system

filesystem

)

include_directories(${Boost_INCLUDE_DIRS})

file( GLOB APP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp )

foreach( sourcefile ${APP_SOURCES} )

file(RELATIVE_PATH filename ${CMAKE_CURRENT_SOURCE_DIR} ${sourcefile})

string(REPLACE ".cpp" "" file ${filename})

add_executable(${file} ${sourcefile})

target_link_libraries(${file} ${Boost_LIBRARIES})

target_link_libraries(${file} pthread)

endforeach( sourcefile ${APP_SOURCES} )

程序输出如下,

70c5cc597502

图片.png

70c5cc597502

图片.png

70c5cc597502

图片.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值