为什么加锁: 我们不能保证发送的接口(一般是逻辑层线程),和回调函数的接口(一般在网络层)在一个线程,就存在两个线程对这个队列的共同访问,所以增加一个锁保证队列安全性.
加什么锁?
std::lock_guard 是 C++ 标准库中的一个类模板,用于管理互斥锁(mutex)的获取和释放。它是一个 RAII(Resource Acquisition Is Initialization)工具,确保在构造时获得锁,并在析构时自动释放锁。这有助于避免死锁,因为它保证了锁的释放即使在抛出异常时也会发生。
#include <mutex>
#include <iostream>
#include <thread>
std::mutex mtx;
void threadSafeFunction(int value) {
std::lock_guard<std::mutex> lock(mtx);
// 临界区开始
std::cout << "Value: " << value << std::endl;
// 临界区结束,lock 被销毁,mtx 自动解锁
}
int main() {
std::thread t1(threadSafeFunction, 1);
std::thread t2(threadSafeFunction, 2);
t1.join();
t2.join();
return 0;
}
t.join()方法只会使主线程(或者说调用t.join()的线程)进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。
设计一个数据节点MsgNode用来存储数据
class MsgNode{
friend class CSession;
public:
MsgNode(char * msg, int max_len) { _
data = new char[max_len];
memcpy(_data, msg, max_len); }
~MsgNode() {
delete[] _data; }
private: int _cur_len; int _max_len; char* _data;
};
1 _cur_len表示数据当前已处理的长度(已经发送的数据或者已经接收的数据长度),因为一个数据包存在未发送完或者未接收完的情况。
2 _max_len表示数据的总长度。
3 _data表示数据域,已接收或者已发送的数据都放在此空间内。
封装发送接口
首先在Session类里新增一个队列存储要发送的数据,因为我们不能保证每次调用发送接口的时候上一次数据已经发送完,就要把要发送的数据放入队列中,通过回调函数不断地发送。而且我们不能保证发送的接口和回调函数的接口在一个线程,所以要增加一个锁保证发送队列安全性。
同时我们新增一个发送接口Send
修改后的session
``
class Session : public std::enable_shared_from_this<Session>//一个模板类
{
public:
Session(boost::asio::io_context& ioc,Server* server) :_socket(ioc),_server(server) {
boost::uuids::uuid a_uuid = boost::uuids::random_generator()();//为什么有两个括号?
_uuid = boost::uuids::to_string(a_uuid);
}
~Session() {
std::cout << "session desturct delete this" << this << std::endl;
}
tcp::socket& Socket() {
return _socket;
}
void Send(char* msg, int max_length);//111111111
void Start();//监听客户端的读和写
std::string& GetUuid();
private:
Server* _server;
std::string _uuid;
tcp::socket _socket;
enum{MAX_LENGTH=1024};
char _data[MAX_LENGTH];
void HandleRead(const boost::system::error_code& ec,size_t bytes_transferred, std::shared_ptr<Session> _self_shared);//当监听到客户端发了数据的时候调用读的回调函数
void HandleWrite(const boost::system::error_code& ec, std::shared_ptr<Session> _self_shared);
std::queue<std::shared_ptr<MsgNode> > _send_que;//2222222222
std::mutex _send_lock;//3333333333
};
发送接口实现
void Session::Send(char* msg, int max_length) {
//注意这里是一个局部变量,所以可以在锁的上面,它不会被共享
bool pending = false;//ing表示发送队列有数据==上一次还没发完
std::lock_guard<std::mutex> lock(_send_lock);
if (_send_que.size() > 0) {//有上次的东西就置true,那样下面封装进节点进队列后就可以判断发上次的还是发这次了
pending = true;
}
_send_que.push(make_shared<MsgNode>(msg, max_length));
if (pending) {//发上次的,直接退出
return;
}
//没东西了发这次的
boost::asio::async_write(_socket, boost::asio::buffer(msg, max_length),
std::bind(&Session::HandleWrite, this, std::placeholders::_1, shared_from_this()));
}
回调写调整
void Session::HandleWrite(const boost::system::error_code& ec, shared_ptr<Session> _self_shared) {
if (!ec) {
std::lock_guard<std::mutex> lock(_send_lock);
_send_que.pop();//为什么直接出队了?因为HandleWrite调用成功了,意味着他的外层函数已经将数据发完了
if (!_send_que.empty()) {//出对一个还有数据节点,那么就拿出来接着发
auto& msgnode = _send_que.front();
boost::asio::async_write(_socket, boost::asio::buffer(msgnode->_data, msgnode->_max_len),
std::bind(&Session::HandleWrite, this, std::placeholders::_1, _self_shared));
}
}
else {
cout << "handle write failed,error is " << endl;
//delete this;//把Session销毁掉,实际这么做有隐患
_server->ClearSession(_uuid);
}
}
回调函数调整做到一直监听
void Session::HandleRead(const boost::system::error_code& ec, size_t bytes_transferred,shared_ptr<Session> _self_shared) {
if (!ec) {//外层异步读函数读完之后,触发读回调,把读到的写回去后初始化数组,继续异步读去监听,这样做到一直监听
cout << "server recervi data is: " << _data << endl;
Send(_data, bytes_transferred);
memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&Session::HandleRead, this, std::placeholders::_1,std::placeholders::_2, _self_shared));
}
else {
cout << "read error" << endl;
//delete this;
_server->ClearSession(_uuid);
}
}
但仍会有粘包问题