gpt4 book ai didi

c++ - 在接受第二个连接后,由于套接字不再存在,导致Boost ASIO段故障

转载 作者:行者123 更新时间:2023-12-02 10:11:36 24 4
gpt4 key购买 nike

我目前正在尝试使用boost::asio获得一个聊天室类型的程序。在当前状态下,服务器可以接受来自客户端的连接,然后客户端可以向服务器发送消息(此时,服务器对消息进行一点格式化,然后将其发送给当前连接的每个客户端) 。
我遇到的问题如下:

server starts
client 0 connects
client 0 sends a message
(the message is received by the server and then sent back to client 0 who receives it correctly)
client 1 connects
client 1 sends a message
(the message is received by the server and then sent back to client 0 and client 1 who both receive it correctly)
client 0 tries to send a message again
(the message is received by the server and the server processes the header then attempts to call async_read again to read the body of the message, however the socket member variable for client 0 no longer exists and I get a segfault)
我发现这真的很奇怪,因为服务器仍然具有客户端0的有效套接字对象(否则它将无法将客户端1的消息发送到客户端0)。
以下是相关代码:
tcp_connection类(发生段故障的位置)
#include <deque>

#include <boost/asio.hpp>
#include <boost/bind.hpp>

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

class tcp_connection {
public:
tcp_connection(tcp::socket socket_, int id, std::function<void (std::size_t, char*, std::size_t)> read_handler)
: socket_(std::move(socket)), id_(id), read_handler_(read_handler) {
}

void start() {
char first_message[] = "server: connected";
net_message msg(first_message, strlen(first_message));
send(msg);

read_header();
}

void send(net_message msg) {
bool write_in_progress = !write_messages_.empty();
write_messages_.push_back(msg);
if (!write_in_progress) {
do_write();
}
}

int get_id() { return id_; }

private:
void read_header() {
boost::asio::async_read(socket_, boost::asio::buffer(read_message_.get_data(), net_message::header_length),
boost::bind(&tcp_connection::handle_read_header, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}

void handle_read_header(const boost::system::error_code e, std::size_t bytes_transferred) {
read_message_.decode_header();
read_body();
}

void read_body() {
/*
######################
THIS IS WHERE THE SEGFAULT OCCURS.
socket_ is no longer valid for some reason
despite socket_ still being valid for any async_write
operations that need to be handled by the do_write() function
######################
*/
boost::asio::async_read(socket_, boost::asio::buffer(read_message_.get_data() + net_message::header_length, read_message_.get_body_length()),
boost::bind(&tcp_connection::handle_read_body, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}

void handle_read_body(const boost::system::error_code e, std::size_t bytes_transferred) {
char body[read_message_.get_body_length()];
memcpy(body, read_message_.get_body(), read_message_.get_body_length());
// call the read_handler from the net_server object
read_handler_(id_, body, read_message_.get_body_length());
read_header();
}

void handle_write(const boost::system::error_code e, std::size_t bytes_transferred) {
}

void do_write() {
boost::asio::async_write(socket_, boost::asio::buffer(write_messages_.front().get_data(),
write_messages_.front().get_body_length() + net_message::header_length),
[this] (boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
write_messages_.pop_front();
if (!write_messages_.empty()) {
do_write();
}
} else {
std::cerr << "error with writing to client " << id_ << " with error code: " << ec << std::endl;
}
});
}

tcp::socket socket_;
std::function<void (std::size_t, char*, std::size_t)> read_handler_;
net_message read_message_;
std::deque<net_message> write_messages_;
int id_;
};
net_server类
class net_server {
public:
net_server(boost::asio::io_context& io_context, std::size_t port,
std::function<void (std::size_t)> accept_handler,
std::function<void (std::size_t, char*, std::size_t)> read_handler)
: io_context_(io_context), acceptor_(io_context, tcp::endpoint(tcp::v4(), 1234)),
accept_handler_(accept_handler), read_handler_(read_handler) {
start_accept();
}

void send_to(std::size_t id, const char* body, std::size_t length) {
net_message msg(body, length);
connections_[id].send(msg);
}

void send_to_all(const char* body, std::size_t length) {
net_message msg(body, length);
for (int i = 0; i < connections_.size(); i++) {
connections_[i].send(msg);
}
}

void send_to_all_except(std::size_t id, const char* body, std::size_t length) {
net_message msg(body, length);
for (int i = 0; i < connections_.size(); i++) {
if (i == id) continue;
connections_[i].send(msg);
}
}

private:
void start_accept() {
acceptor_.async_accept(
[this](boost::system::error_code ec, tcp::socket socket) {
if (!ec) {
std::unique_lock lock(connections_mutex_);
std::size_t index = connections_.size();
connections_.emplace_back(std::move(socket), connections_.size(), read_handler_);
lock.unlock();

connections_[index].start();
accept_handler_(index);
}
start_accept();
});
}

boost::asio::io_context& io_context_;
tcp::acceptor acceptor_;

std::vector<tcp_connection> connections_;
std::mutex connections_mutex_;

std::function<void (std::size_t)> accept_handler_;
std::function<void (std::size_t, char*, std::size_t)> read_handler_;
};
设置服务器的主要cpp程序
#include <iostream>

class client {
public:
client()
: valid_(false)
{}
client(int id)
: id_(id), valid_(true)
{}

const char * get_name() const {
std::string str("Client ");
str += std::to_string(id_);
return str.c_str();
}
private:
int id_;
bool valid_;
};

class chat_server {
public:
chat_server(boost::asio::io_context& io_context, std::size_t port)
: server_(io_context, port, std::bind(&chat_server::handle_accept, this, std::placeholders::_1),
std::bind(&chat_server::handle_read, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))
{}

void handle_accept(std::size_t client_index) {
std::scoped_lock lock(clients_mutex_);
if (clients_.size() != client_index) {
std::cerr << "New client connecting at index " << client_index <<
" however, clients_ vector currently has size " << clients_.size() << std::endl;
if (clients_.size() < client_index) {
clients_.resize(client_index);
clients_.emplace_back(client_index);
} else {
clients_[client_index] = client(client_index);
}
} else {
clients_.emplace_back(client_index);
}
std::cout << "New client with id: " << client_index << std::endl;
}

void handle_read(std::size_t sender, char* body, std::size_t length) {
// whenever the server receives a message, this function will be called
// where clients[sender] will be the connection that sent the message
// body will be a pointer to the start of the body of the message
// and length will be the length of the body
// we will process the message here and decide if / what to send in response
// (for example, in a chat server, we'd want to forward the message to every client
// with the name of the sender attached to it so that clients can update the chat dialogue)
std::size_t sender_name_len = strlen(clients_[sender].get_name());
std::size_t new_message_length = sender_name_len + length + 3;
char new_message[new_message_length];

sprintf(new_message, "%s: ", clients_[sender].get_name());
memcpy(new_message + sender_name_len + 2, body, length);
new_message[new_message_length - 1] = '\0';

std::cout << new_message << std::endl;

server_.send_to_all(new_message, new_message_length-1);
}
private:
net_server server_;
std::vector<client> clients_;
std::mutex clients_mutex_;
};

int main() {
try {
boost::asio::io_context io_context;
chat_server serv(io_context, 1234);
io_context.run();
} catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}

return 0;
}
我要为服务器类维护一个tcp_connections列表,每个列表代表一个已连接到服务器的客户端。当服务器接受连接时,将为该连接创建一个tcp_connection对象,然后该tcp_connection对象将启动一个无限的异步“read_header-> read_body-> repeat”循环。每当服务器从任何客户端接收到消息时,它都应格式化该消息,然后将其发送到列表中的每个tcp_connection。

最佳答案

向其添加新元素时,将重新分配connections_成员变量。在tcp_connection中的各种处理程序中,您正在捕获this,当重新分配 vector 时,this的值将更改,然后您的处理程序将尝试对对象的旧副本进行操作,从而导致未定义的行为。
简单的解决方案是使connections_ vector 成为std::shared_ptr vector 。
最好的做法是在处理程序中捕获对象的shared_ptr,以使对象在执行回调之前不会超出范围。例如。:

void do_write() {
auto self = shared_from_this();
boost::asio::async_write(socket_, boost::asio::buffer(write_messages_.front().get_data(),
write_messages_.front().get_body_length() + net_message::header_length),
[self, this] (boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
write_messages_.pop_front();
if (!write_messages_.empty()) {
do_write();
}
} else {
std::cerr << "error with writing to client " << id_ << " with error code: " << ec << std::endl;
}
});
}
您需要从 tcp_connection派生 std::shared_from_this<tcp_connection>并确保在设置任何处理程序之前已创建 shared_ptr(例如,不要在构造函数中创建处理程序)。

关于c++ - 在接受第二个连接后,由于套接字不再存在,导致Boost ASIO段故障,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63334208/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com